November 22nd, 2024

Goal

Package CASPER for macOS. Fix bugs to enhance functionality and user experience.

Hypothesis

If the application is packaged and made available for macOS, users will be able to run CASPER on their macOS computers.

If the bugs are resolved, users will be able to operate the program without experiencing crashes.

Expected Results

Results

Bugs Fixed

Special thanks to David for stress-testing the program last week. The following issues have been resolved:

Next Steps

My next step is to address any bugs that David may encounter during testing. I’ll focus on stabilizing the modules with outstanding issues and proceed with packaging the app for Windows. Additionally, I will work with David to make plans for the implementation of the Microbiome Analysis module.

Files changed (76) hide show
  1. assets/plus_dark.png +0 -0
  2. assets/plus_white.png +0 -0
  3. assets/settings_dark.png +0 -0
  4. assets/settings_light.png +0 -0
  5. mac.spec → scripts/mac.spec +69 -61
  6. src/controllers/CoTargetingController.py +71 -0
  7. src/controllers/ExportSelectedgRNAsController.py +223 -0
  8. src/controllers/FindTargetsController.py +73 -8
  9. src/controllers/GenerateLibraryController.py +229 -0
  10. src/controllers/HomeWindowController.py +173 -57
  11. src/controllers/MainWindowController.py +114 -52
  12. src/controllers/MultitargetingWindowController.py +79 -8
  13. src/controllers/NCBIWindowController.py +1 -1
  14. src/controllers/NewEndonucleaseController.py +1 -0
  15. src/controllers/OffTarget.py +0 -277
  16. src/controllers/OffTargetController.py +253 -0
  17. src/controllers/PopulationAnalysisWindowController.py +61 -17
  18. src/controllers/StartupWindowController.py +15 -2
  19. src/controllers/ViewTargetsController.py +728 -266
  20. src/controllers/populationAnalysis.py +0 -929
  21. src/main.py +43 -16
  22. src/models/AnnotationParser.py +124 -51
  23. src/models/CoTargetingModel.py +50 -0
  24. src/models/ConfigManager.py +7 -8
  25. src/models/DatabaseManager.py +113 -45
  26. src/models/ExportSelectedgRNAsModel.py +116 -0
  27. src/models/FindTargetsModel.py +247 -70
  28. src/models/GenerateLibraryModel.py +239 -0
  29. src/models/GlobalSettings.py +175 -40
  30. src/models/HomeWindowModel.py +76 -28
  31. src/models/MainWindowModel copy.py +0 -75
  32. src/models/MultitargetingWindowModel.py +154 -4
  33. src/models/NewEndonucleaseModel.py +2 -2
  34. src/models/NewGenomeWindowModel.py +1 -2
  35. src/{OffTargetFolder → models/OffTarget}/OT_Lin +0 -0
  36. src/{OffTargetFolder → models/OffTarget}/OT_Mac +0 -0
  37. src/{OffTargetFolder → models/OffTarget}/OT_Win.exe +0 -0
  38. src/models/OffTarget/local_output.txt +29 -0
  39. src/{OffTargetFolder → models/OffTarget}/sqlite3.dll +0 -0
  40. src/models/OffTargetModel.py +457 -0
  41. src/models/PopulationAnalysisWindowModel.py +117 -42
  42. src/models/StartupWindowModel.py +2 -2
  43. src/models/ViewTargetsModel.py +238 -116
  44. src/ui/cotargeting.ui +29 -94
  45. src/ui/{export_tool.ui → export_selected_gRNAs.ui} +24 -89
  46. src/ui/find_targets.ui +26 -1
  47. src/ui/generate_library.ui +55 -69
  48. src/ui/home_window.ui +32 -178
  49. src/ui/home_window_copy.ui +0 -946
  50. src/ui/multitargeting_sql_settings.ui +0 -149
  51. src/ui/multitargeting_stats.ui +0 -227
  52. src/ui/multitargeting_window.ui +186 -84
  53. src/ui/{ncbi_window_v2.ui → ncbi.ui} +0 -0
  54. src/ui/ncbi_window.ui +0 -335
  55. src/ui/ncbi_window_v2 copy.ui +0 -495
  56. src/ui/new_endonuclease_window.ui +1 -1
  57. src/ui/new_genome_window_old.ui +0 -823
  58. src/ui/off_target.ui +111 -269
  59. src/ui/population_analysis.ui +36 -55
  60. src/ui/view_targets.ui +99 -269
  61. src/utils/ui.py +25 -18
  62. src/views/CoTargetingView.py +73 -0
  63. src/views/ExportSelectedgRNAsView.py +59 -0
  64. src/views/FindTargetsView.py +38 -7
  65. src/views/GenBankParse.py +0 -82
  66. src/views/GenerateLibraryView.py +182 -0
  67. src/views/HomeWindowView.py +86 -12
  68. src/views/MainWindowUI.py +0 -152
  69. src/views/MainWindowView copy.py +0 -265
  70. src/views/MainWindowView.py +166 -15
  71. src/views/MultitargetingWindowView.py +53 -41
  72. src/views/NCBIWindowView.py +1 -1
  73. src/views/NewEndonucleaseView.py +126 -1
  74. src/views/OffTargetView.py +138 -0
  75. src/views/PopulationAnalysisWindowView.py +182 -64
  76. src/views/ViewTargetsView.py +253 -164
assets/plus_dark.png ADDED
Binary file
 
assets/plus_white.png ADDED
Binary file
 
assets/settings_dark.png ADDED
Binary file
 
assets/settings_light.png ADDED
Binary file
 
mac.spec → scripts/mac.spec RENAMED
@@ -1,61 +1,69 @@
1
- # -*- mode: python ; coding: utf-8 -*-
2
- block_cipher = None
3
-
4
-
5
- a = Analysis(['main.py'],
6
- pathex=[],
7
- datas=[
8
- ('OffTargetFolder', 'OffTargetFolder'),
9
- ('SeqFinderFolder', 'SeqFinderFolder'),
10
- ('controllers', 'controllers'),
11
- ('models', 'models'),
12
- ('utils', 'utils'),
13
- ('views', 'views'),
14
- ('ui', 'ui'),
15
- ('CASPERinfo', '.'),
16
- ('assets', 'assets'),
17
- ('genomeBrowserTemplate.html', '.'),
18
- ('logs', 'logs')
19
- ],
20
- hiddenimports=[],
21
- hookspath=[],
22
- runtime_hooks=[],
23
- excludes=[],
24
- win_no_prefer_redirects=False,
25
- win_private_assemblies=False,
26
- cipher=block_cipher,
27
- noarchive=False)
28
-
29
- pyz = PYZ(a.pure, a.zipped_data,
30
- cipher=block_cipher)
31
-
32
- exe = EXE(pyz,
33
- a.scripts,
34
- [],
35
- exclude_binaries=True,
36
- name='CASPERapp',
37
- debug=False,
38
- bootloader_ignore_signals=False,
39
- strip=False,
40
- upx=True,
41
- console=False,
42
- disable_windowed_traceback=False,
43
- target_arch=None,
44
- codesign_identity=None,
45
- entitlements_file=None,
46
- icon='assets/CASPER_icon.icns')
47
-
48
- coll = COLLECT(exe,
49
- a.binaries,
50
- a.zipfiles,
51
- a.datas,
52
- strip=False,
53
- upx=True,
54
- upx_exclude=[],
55
- name='CASPERapp')
56
-
57
- app = BUNDLE(coll,
58
- name='CASPERapp.app',
59
- icon='assets/CASPER_icon.icns',
60
- version='2.0.1',
61
- bundle_identifier=None)
 
 
 
 
 
 
 
 
 
1
+ block_cipher = None
2
+
3
+ a = Analysis(['src/main.py'],
4
+ pathex=['src'],
5
+ datas=[
6
+ ('assets', 'assets'),
7
+ ('config', 'config'),
8
+ ('logs', 'logs'),
9
+ ('src', 'src'),
10
+ ('genomeBrowserTemplate.html', '.'),
11
+ ],
12
+ hiddenimports=[],
13
+ hookspath=[],
14
+ runtime_hooks=[],
15
+ excludes=[],
16
+ win_no_prefer_redirects=False,
17
+ win_private_assemblies=False,
18
+ cipher=block_cipher,
19
+ noarchive=False)
20
+
21
+ pyz = PYZ(a.pure, a.zipped_data,
22
+ cipher=block_cipher)
23
+
24
+ exe = EXE(pyz,
25
+ a.scripts,
26
+ [],
27
+ exclude_binaries=True,
28
+ name='CASPERapp',
29
+ debug=False,
30
+ bootloader_ignore_signals=False,
31
+ strip=False,
32
+ upx=True,
33
+ console=False,
34
+ disable_windowed_traceback=False,
35
+ target_arch=None,
36
+ codesign_identity=None,
37
+ entitlements_file=None,
38
+ icon='assets/CASPER_icon.icns')
39
+
40
+ coll = COLLECT(exe,
41
+ a.binaries,
42
+ a.zipfiles,
43
+ a.datas,
44
+ strip=False,
45
+ upx=True,
46
+ upx_exclude=[],
47
+ name='CASPERapp')
48
+
49
+ app = BUNDLE(coll,
50
+ name='CASPERapp.app',
51
+ icon='assets/CASPER_icon.icns',
52
+ version='2.0.1',
53
+ bundle_identifier=None)
54
+
55
+ # 1. Have the mac.spec in the app directory
56
+ # 2. pyinstaller mac.spec
57
+ # 3. mkdir -p dist/dmg
58
+ # 4. rm -r dist/dmg/*
59
+ # 5. Manual copy of the app into dist/dmg
60
+ # 6. create-dmg \
61
+ # --volname "CASPERapp" \
62
+ # --window-pos 200 120 \
63
+ # --window-size 600 300 \
64
+ # --icon-size 100 \
65
+ # --icon "CASPERapp.app" 175 120 \
66
+ # --hide-extension "CASPERapp.app" \
67
+ # --app-drop-link 425 120 \
68
+ # "dist/CASPERapp.dmg" \
69
+ # "dist/dmg/"
src/controllers/CoTargetingController.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from models.CoTargetingModel import CoTargetingModel
2
+ from views.CoTargetingView import CoTargetingView
3
+
4
+ class CoTargetingController:
5
+ def __init__(self, global_settings, view_targets_controller=None):
6
+ self.settings = global_settings
7
+ self.logger = global_settings.get_logger()
8
+ self.model = CoTargetingModel(global_settings)
9
+ self.view = CoTargetingView(global_settings)
10
+ self.view_targets_controller = view_targets_controller
11
+
12
+ # Connect signals
13
+ self.view.push_button_cancel.clicked.connect(self.cancel)
14
+ self.view.push_button_submit.clicked.connect(self.submit)
15
+
16
+ def show(self):
17
+ """Show the co-targeting window"""
18
+ self.view.show()
19
+ self.view.activateWindow()
20
+
21
+ def launch(self, endo_choices, org_name):
22
+ """Launch co-targeting analysis"""
23
+ try:
24
+ self.view.line_edit_organism.setText(org_name)
25
+ self.view.populate_table(endo_choices)
26
+ self.show()
27
+ self.view.activateWindow()
28
+ except Exception as e:
29
+ self.logger.error(f"Error launching co-targeting: {str(e)}")
30
+ self.view.show_error("Launch Error", str(e))
31
+
32
+ def submit(self):
33
+ try:
34
+ selected_endos = self.view.get_selected_endonucleases()
35
+
36
+ if len(selected_endos) <= 1:
37
+ self.view.show_error(
38
+ "Nothing Selected",
39
+ "No endonucleases selected. Please select at least 2 endonucleases"
40
+ )
41
+ return
42
+
43
+ # Validate compatibility
44
+ if not self.model.validate_endonucleases(selected_endos):
45
+ self.view.show_error(
46
+ "Invalid Endonucleases",
47
+ "The selected endonucleases are not compatible."
48
+ )
49
+ return
50
+
51
+ # Update view targets controller with selected endonucleases
52
+ if self.view_targets_controller:
53
+ self.view_targets_controller.handle_cotargeting_result(selected_endos)
54
+ else:
55
+ self.logger.error("No view_targets_controller available")
56
+ self.view.show_error("Error", "Could not update targets view")
57
+ return
58
+
59
+ self.cancel()
60
+
61
+ except Exception as e:
62
+ self.logger.error(f"Error in submit: {str(e)}")
63
+ self.view.show_error("Submit Error", str(e))
64
+
65
+ def cancel(self):
66
+ """Handle cancel button click"""
67
+ try:
68
+ self.view.clear()
69
+ self.view.hide()
70
+ except Exception as e:
71
+ self.logger.error(f"Error in cancel: {str(e)}")
src/controllers/ExportSelectedgRNAsController.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import platform
3
+ from PyQt6.QtWidgets import QFileDialog
4
+ from utils.ui import show_message, show_error
5
+ from models.ExportSelectedgRNAsModel import ExportSelectedgRNAsModel
6
+ from views.ExportSelectedgRNAsView import ExportSelectedgRNAsView
7
+
8
+ class ExportSelectedgRNAsController:
9
+ def __init__(self, global_settings):
10
+ self.settings = global_settings
11
+ self.logger = self.settings.get_logger()
12
+ try:
13
+ self.view = ExportSelectedgRNAsView(self.settings)
14
+ self.model = ExportSelectedgRNAsModel(self.settings)
15
+ self._setup_connections()
16
+ except Exception as e:
17
+ show_error(self.settings, "Error initializing ExportSelectedgRNAsController", str(e))
18
+
19
+ def _setup_connections(self) -> None:
20
+ try:
21
+ self.view.push_button_export.clicked.connect(self._handle_export)
22
+ self.view.push_button_cancel.clicked.connect(self._handle_cancel)
23
+ self.view.push_button_browse.clicked.connect(self._handle_browse)
24
+ except Exception as e:
25
+ self.logger.error(f"Error setting up connections: {str(e)}")
26
+ raise
27
+
28
+ def show_dialog(self, selected_items: list, window_type: str) -> None:
29
+ try:
30
+ # Reset the form fields
31
+ self.view.line_edit_file_name.clear()
32
+ self.view.line_edit_leading_sequence.clear()
33
+ self.view.line_edit_trailing_sequence.clear()
34
+
35
+ # Set default path
36
+ default_path = self.settings.get_db_path()
37
+ self.view.set_file_path(default_path)
38
+
39
+ # Set new data
40
+ self.model.set_export_data(selected_items, window_type)
41
+
42
+ # Show and bring to front
43
+ self.view.show()
44
+ self.view.raise_()
45
+ self.view.activateWindow()
46
+
47
+ except Exception as e:
48
+ show_error(self.settings, "Error showing export dialog", str(e))
49
+
50
+ def _handle_browse(self) -> None:
51
+ try:
52
+ directory = QFileDialog.getExistingDirectory(
53
+ self.view,
54
+ "Select Export Directory",
55
+ self.settings.CSPR_DB,
56
+ QFileDialog.Option.ShowDirsOnly
57
+ )
58
+
59
+ if directory:
60
+ directory += "\\" if platform.system() == "Windows" else "/"
61
+ self.view.set_file_path(directory)
62
+ except Exception as e:
63
+ show_error(self.settings, "Error browsing for directory", str(e))
64
+
65
+ def _handle_export(self) -> None:
66
+ try:
67
+ settings = self.view.get_export_settings()
68
+
69
+ if not settings['file_name']:
70
+ settings['file_name'] = "exported_gRNAs"
71
+
72
+ full_path = self.model.get_full_path(
73
+ settings['file_path'],
74
+ settings['file_name'],
75
+ settings['delimiter']
76
+ )
77
+
78
+ self._write_export_file(full_path, settings)
79
+
80
+ show_message(
81
+ "Export Complete",
82
+ f"Export to {full_path} was successful."
83
+ )
84
+
85
+ self._handle_cancel()
86
+
87
+ except PermissionError:
88
+ show_error(self.settings, "Permission Error",
89
+ "Cannot access the file. Please ensure it is not open elsewhere.")
90
+ except Exception as e:
91
+ show_error(self.settings, "Export Error", str(e))
92
+
93
+ def _write_export_file(self, full_path: str, settings: dict) -> None:
94
+ with open(full_path, 'w') as output_file:
95
+ delimiter = "\t" if settings['delimiter'] == r"\t" else settings['delimiter']
96
+ headers = self.model.get_headers()
97
+ output_file.write(delimiter.join(headers) + "\n")
98
+ self._write_data_rows(output_file, headers, settings)
99
+
100
+ def _write_data_rows(self, output_file, headers: list, settings: dict) -> None:
101
+ """Write data rows to the output file"""
102
+ try:
103
+ delimiter = "\t" if settings['delimiter'] == r"\t" else settings['delimiter']
104
+
105
+ for item in self.model.data['selected_items']:
106
+ row_data = []
107
+
108
+ if self.model.data['window_type'] == "Multitargeting":
109
+ for header in headers:
110
+ if header == "Seed":
111
+ row_data.append(str(item['seed']))
112
+ elif header == "Total Repeats":
113
+ row_data.append(str(item['total_repeats']))
114
+ elif header == "Avg. Repeats/Scaffold":
115
+ row_data.append(str(item['avg_repeats_or_scaffold']))
116
+ elif header == "Consensus Sequence":
117
+ row_data.append(str(item['consensus_sequence']))
118
+ elif header == "Full Sequence":
119
+ sequence = str(item['consensus_sequence'])
120
+ full_sequence = (settings['leading_sequence'] +
121
+ sequence +
122
+ settings['trailing_sequence'])
123
+ row_data.append(full_sequence)
124
+ elif header == "% Consensus":
125
+ row_data.append(str(item['percent_consensus']))
126
+ elif header == "Score":
127
+ row_data.append(str(item['score']))
128
+ elif header == "PAM":
129
+ row_data.append(str(item['pam']))
130
+ elif header == "Strand":
131
+ row_data.append(str(item['strand']))
132
+ else:
133
+ row_data.append("")
134
+
135
+ elif self.model.data['window_type'] == "Population Analysis":
136
+ # Handle Population Analysis specific data format
137
+ for header in headers:
138
+ if header == "Seed":
139
+ row_data.append(str(item['seed']))
140
+ elif header == "% Coverage":
141
+ row_data.append(str(item['percent_coverage']))
142
+ elif header == "Total Repeats":
143
+ row_data.append(str(item['total_repeats']))
144
+ elif header == "Avg. Repeats/Scaffold":
145
+ row_data.append(str(item['avg_repeats_or_scaffold']))
146
+ elif header == "Consensus Sequence":
147
+ row_data.append(str(item['consensus_sequence']))
148
+ elif header == "Full Sequence":
149
+ sequence = str(item['consensus_sequence'])
150
+ full_sequence = (settings['leading_sequence'] +
151
+ sequence +
152
+ settings['trailing_sequence'])
153
+ row_data.append(full_sequence)
154
+ elif header == "% Consensus":
155
+ row_data.append(str(item['percent_consensus']))
156
+ elif header == "Score":
157
+ row_data.append(str(item['score']))
158
+ elif header == "PAM":
159
+ row_data.append(str(item['pam']))
160
+ elif header == "Strand":
161
+ row_data.append(str(item['strand']))
162
+ else:
163
+ row_data.append("-")
164
+ else:
165
+ # Handle View Targets window
166
+ for header in headers:
167
+ if header == "Location":
168
+ row_data.append(str(item['location']))
169
+ elif header == "Endonuclease":
170
+ row_data.append(str(item['endonuclease']))
171
+ elif header == "Sequence":
172
+ row_data.append(str(item['sequence']))
173
+ elif header == "Full Sequence":
174
+ sequence = str(item['sequence'])
175
+ full_sequence = (settings['leading_sequence'] +
176
+ sequence +
177
+ settings['trailing_sequence'])
178
+ row_data.append(full_sequence)
179
+ elif header == "Strand":
180
+ row_data.append(str(item['strand']))
181
+ elif header == "PAM":
182
+ row_data.append(str(item['pam']))
183
+ elif header == "Score":
184
+ row_data.append(str(item['score']))
185
+ elif header == "Off-Target":
186
+ row_data.append(str(item.get('off_target', '--.--')))
187
+ elif header == "Locus_Tag":
188
+ row_data.append(str(item.get('locus_tag', '')))
189
+ elif header == "Gene_Name":
190
+ row_data.append(str(item.get('gene_name', '')))
191
+ else:
192
+ row_data.append("")
193
+
194
+ output_file.write(delimiter.join(row_data) + "\n")
195
+
196
+ except Exception as e:
197
+ self.logger.error(f"Error writing data rows: {str(e)}")
198
+ self.logger.error(f"Data item causing error: {item}")
199
+ self.logger.error(f"Headers: {headers}")
200
+ raise
201
+
202
+ def _handle_end_of_row(self, tmp_list: list, output_file, delimiter: str) -> None:
203
+ """Handle end of row processing"""
204
+ tmp_list.append(self.model.data['selected_items'][-1].text())
205
+ if self.model.data['has_locus_tag']:
206
+ tmp = self.settings.mainWindow.Results.comboBoxGene.currentText().split(":")
207
+ tmp_list.extend([tmp[0].strip(), tmp[-1].strip()])
208
+ elif self.model.data['has_gene_name']:
209
+ tmp_list.append(self.settings.mainWindow.Results.comboBoxGene.currentText().strip())
210
+
211
+ delimiter = "\t" if delimiter == r"\t" else delimiter
212
+ output_file.write(delimiter.join(tmp_list) + "\n")
213
+
214
+ def _handle_sequence_column(self, tmp_list: list, item, settings: dict) -> None:
215
+ sequence = item.get('sequence', '') if isinstance(item, dict) else item.text()
216
+ tmp_list.append(sequence)
217
+ full_sequence = (settings['leading_sequence'] +
218
+ sequence +
219
+ settings['trailing_sequence'])
220
+ tmp_list.append(full_sequence)
221
+
222
+ def _handle_cancel(self) -> None:
223
+ self.view.hide()
src/controllers/FindTargetsController.py CHANGED
@@ -1,4 +1,5 @@
1
  from models.FindTargetsModel import FindTargetsModel
 
2
  from views.FindTargetsView import FindTargetsView
3
  from PyQt6.QtWidgets import QMessageBox
4
  from PyQt6.QtCore import QTimer
@@ -103,20 +104,84 @@ class FindTargetsController:
103
  try:
104
  if not self.view:
105
  return
106
-
107
  selected_targets = self.view.get_selected_targets()
108
- print(f"Selected targets: {selected_targets}")
109
- print(f"Organism: {self.organism}")
110
- print(f"Endonuclease: {self.endonuclease}")
111
  if not selected_targets:
112
  QMessageBox.warning(self.view, "No Selection", "Please select targets to view.")
113
  return
 
 
 
 
114
 
115
- view_targets_controller = self.global_settings.get_view_targets_window()
116
- view_targets_controller.load_targets(selected_targets, self.organism, self.endonuclease)
117
- self.global_settings.main_window.open_new_tab("View Targets", view_targets_controller)
118
-
 
 
 
 
 
 
 
 
 
 
 
 
119
  except Exception as e:
120
  self.global_settings.logger.error(f"Error in view_targets: {str(e)}")
121
  if self.view:
122
  QMessageBox.critical(self.view, "Error", f"An error occurred while viewing targets: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from models.FindTargetsModel import FindTargetsModel
2
+ from utils.ui import show_error
3
  from views.FindTargetsView import FindTargetsView
4
  from PyQt6.QtWidgets import QMessageBox
5
  from PyQt6.QtCore import QTimer
 
104
  try:
105
  if not self.view:
106
  return
107
+
108
  selected_targets = self.view.get_selected_targets()
 
 
 
109
  if not selected_targets:
110
  QMessageBox.warning(self.view, "No Selection", "Please select targets to view.")
111
  return
112
+
113
+ # Find existing View Targets tab
114
+ main_window = self.global_settings.main_window
115
+ existing_tab = main_window.find_tab_by_title("View Targets")
116
 
117
+ if existing_tab:
118
+ # Get the existing controller from main window's tab_widgets
119
+ view_targets_controller = main_window.tab_widgets['controllers'].get("View Targets")
120
+ if view_targets_controller:
121
+ # Update existing view with new targets
122
+ view_targets_controller.load_targets(selected_targets, self.organism, self.endonuclease)
123
+ # Switch to the existing tab
124
+ main_window.view.tab_widget.setCurrentWidget(existing_tab)
125
+ else:
126
+ self.logger.error("View Targets controller not found for existing tab")
127
+ else:
128
+ # Create new View Targets tab if none exists
129
+ view_targets_controller = self.global_settings.get_view_targets_window()
130
+ view_targets_controller.load_guides(selected_targets, self.organism, self.endonuclease)
131
+ main_window.open_new_tab("View Targets", view_targets_controller)
132
+
133
  except Exception as e:
134
  self.global_settings.logger.error(f"Error in view_targets: {str(e)}")
135
  if self.view:
136
  QMessageBox.critical(self.view, "Error", f"An error occurred while viewing targets: {str(e)}")
137
+
138
+ def gather_settings(self):
139
+ """Process input data and direct to appropriate view"""
140
+ try:
141
+ input_data = self.view.get_find_targets_input()
142
+
143
+ # For position-based searches, go directly to view targets
144
+ if input_data['search_type'] == 'position':
145
+ self.open_view_targets_directly(input_data)
146
+ else:
147
+ # For other search types, show find targets view first
148
+ self.find_targets(input_data)
149
+
150
+ except Exception as e:
151
+ show_error(self.global_settings, "Error in find_targets", str(e))
152
+
153
+ def open_view_targets_directly(self, input_data):
154
+ """Open view targets directly for position-based searches"""
155
+ try:
156
+ # Get targets using the model
157
+ targets = self.model.find_targets_by_position(
158
+ self.model._get_parser(self.model.get_cspr_file_path(input_data)),
159
+ input_data
160
+ )
161
+
162
+ if targets:
163
+ # Create view targets controller
164
+ view_targets_controller = self.global_settings.get_view_targets_window()
165
+
166
+ # Load targets directly
167
+ view_targets_controller.load_targets(
168
+ targets,
169
+ input_data['organism'],
170
+ input_data['endonuclease']
171
+ )
172
+
173
+ # Open view targets tab
174
+ self.global_settings.main_window.open_new_tab(
175
+ "View Targets",
176
+ view_targets_controller
177
+ )
178
+ else:
179
+ QMessageBox.warning(
180
+ self.view,
181
+ "No Targets Found",
182
+ "No targets were found in the specified position range."
183
+ )
184
+
185
+ except Exception as e:
186
+ self.global_settings.logger.error(f"Error opening view targets directly: {str(e)}")
187
+ show_error(self.global_settings, "Error", f"Could not open view targets: {str(e)}")
src/controllers/GenerateLibraryController.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from views.GenerateLibraryView import GenerateLibraryView
2
+ from models.GenerateLibraryModel import GenerateLibraryModel
3
+ from PyQt6.QtCore import QObject
4
+ import os
5
+
6
+ class GenerateLibraryController(QObject):
7
+ def __init__(self, global_settings, selected_targets=None):
8
+ # Initialize QObject first
9
+ super(GenerateLibraryController, self).__init__()
10
+
11
+ self.global_settings = global_settings
12
+ self.logger = global_settings.logger
13
+ self.model = GenerateLibraryModel(global_settings)
14
+ self.view = GenerateLibraryView(global_settings)
15
+
16
+ # Get CSPR file path
17
+ if selected_targets and len(selected_targets) > 0:
18
+ # Get organism name from the first target's chromosome
19
+ chrom = selected_targets[0].get('full_chromosome', '')
20
+ if chrom:
21
+ # Extract organism name from chromosome ID
22
+ org_name = chrom.split('.')[0]
23
+ default_filename = f"{org_name}_lib.csv"
24
+ self.view.ledFileName.setText(default_filename)
25
+
26
+ # Get CSPR file path and initialize parser
27
+ org_files = self.model.get_organism_to_files()
28
+ endonuclease = selected_targets[0].get('endonuclease', '').lower()
29
+ if org_name in org_files and endonuclease in org_files[org_name]:
30
+ cspr_file = os.path.join(
31
+ self.global_settings.get_db_path(),
32
+ org_files[org_name][endonuclease][0]
33
+ )
34
+ self.model.initialize_parser(cspr_file)
35
+
36
+ # Get guide data for each target
37
+ processed_targets = []
38
+ for target in selected_targets:
39
+ target_info = [{
40
+ 'feature_id': target['feature_id'],
41
+ 'feature_name': target['feature_name'],
42
+ 'start': target['start'],
43
+ 'end': target['end'],
44
+ 'chromosome': target['chromosome']
45
+ }]
46
+ guides = self.model.parser.read_targets_batch(
47
+ target['chromosome'],
48
+ target_info,
49
+ target['endonuclease']
50
+ )
51
+ if guides:
52
+ for guide in guides:
53
+ guide.update({
54
+ 'feature_id': target['feature_id'],
55
+ 'feature_name': target['feature_name'],
56
+ 'feature_type': target['feature_type'],
57
+ 'feature_description': target['feature_description'],
58
+ 'start': target['start'],
59
+ 'end': target['end']
60
+ })
61
+ processed_targets.extend(guides)
62
+
63
+ self.selected_targets = processed_targets
64
+ else:
65
+ self.selected_targets = selected_targets
66
+ else:
67
+ self.selected_targets = selected_targets
68
+
69
+ # Log initialization
70
+ self.logger.debug(f"Initializing GenerateLibraryController with {len(selected_targets) if selected_targets else 0} targets")
71
+
72
+ self._connect_signals()
73
+
74
+ def _connect_signals(self):
75
+ """Connect view signals to controller methods"""
76
+ try:
77
+ self.view.submit_clicked.connect(self._handle_submit)
78
+ self.logger.debug("Connected GenerateLibraryView signals")
79
+ except Exception as e:
80
+ self.logger.error(f"Error connecting signals: {str(e)}")
81
+
82
+ def show(self):
83
+ """Show the generate library window"""
84
+ try:
85
+ self.logger.debug("Showing GenerateLibraryView")
86
+
87
+ # Store reference to prevent garbage collection
88
+ self.global_settings.main_window._current_generate_library_controller = self
89
+
90
+ # Show the view
91
+ self.view.show()
92
+
93
+ except Exception as e:
94
+ self.logger.error(f"Error showing generate library window: {str(e)}")
95
+
96
+ def _handle_submit(self, settings):
97
+ """Handle submit button click"""
98
+ try:
99
+ self.logger.debug(f"Handling submit with settings: {settings}")
100
+
101
+ # Validate settings
102
+ self._validate_settings(settings)
103
+
104
+ # Get guide data for targets if not already processed
105
+ if not hasattr(self, 'processed_targets'):
106
+ self.processed_targets = []
107
+
108
+ # Get CSPR file path
109
+ if self.selected_targets and len(self.selected_targets) > 0:
110
+ first_target = self.selected_targets[0]
111
+ self.logger.debug(f"First target: {first_target}")
112
+
113
+ # Get organism name from the main window's home window view
114
+ home_window = self.global_settings._current_home_window
115
+ org_name = home_window.view.combo_box_organism.currentText()
116
+ endonuclease = first_target['endonuclease'].lower()
117
+
118
+ self.logger.debug(f"Looking for CSPR file for {org_name} and {endonuclease}")
119
+
120
+ # Get CSPR file path
121
+ org_files = self.model.get_organism_to_files()
122
+ if org_name in org_files:
123
+ # Debug available endonucleases
124
+ self.logger.debug(f"Available endonucleases for {org_name}: {list(org_files[org_name].keys())}")
125
+
126
+ # Try both lowercase and original case
127
+ cspr_file = None
128
+ if endonuclease in org_files[org_name]:
129
+ cspr_file = org_files[org_name][endonuclease][0]
130
+ elif first_target['endonuclease'] in org_files[org_name]:
131
+ cspr_file = org_files[org_name][first_target['endonuclease']][0]
132
+
133
+ if cspr_file:
134
+ cspr_path = os.path.join(self.global_settings.get_db_path(), cspr_file)
135
+ self.logger.debug(f"Using CSPR file: {cspr_path}")
136
+
137
+ self.model.initialize_parser(cspr_path)
138
+
139
+ # Process each target to get guide data
140
+ for target in self.selected_targets:
141
+ # Get start and end from location if not present
142
+ if 'start' not in target or 'end' not in target:
143
+ if 'location' in target:
144
+ start, end = map(int, target['location'].split('-'))
145
+ target['start'] = start
146
+ target['end'] = end
147
+
148
+ target_info = [{
149
+ 'feature_id': target['feature_id'],
150
+ 'feature_name': target['feature_name'],
151
+ 'start': int(target['start']),
152
+ 'end': int(target['end']),
153
+ 'chromosome': target['chromosome']
154
+ }]
155
+
156
+ self.logger.debug(f"Searching guides for target: {target_info}")
157
+
158
+ # Get guides for this target
159
+ guides = self.model.parser.read_targets_batch(
160
+ target['chromosome'],
161
+ target_info,
162
+ target['endonuclease']
163
+ )
164
+
165
+ if guides:
166
+ self.logger.debug(f"Found {len(guides)} guides for target {target['feature_id']}")
167
+ # Add target info to each guide
168
+ for guide in guides:
169
+ guide.update({
170
+ 'feature_id': target['feature_id'],
171
+ 'feature_name': target['feature_name'],
172
+ 'feature_type': target.get('feature_type', 'CDS'),
173
+ 'feature_description': target.get('feature_description', ''),
174
+ 'start': int(target['start']),
175
+ 'end': int(target['end'])
176
+ })
177
+ self.processed_targets.extend(guides)
178
+ else:
179
+ self.logger.warning(f"No guides found for target {target['feature_id']}")
180
+
181
+ self.logger.debug(f"Processed {len(self.processed_targets)} total guides from CSPR file")
182
+ else:
183
+ raise ValueError(f"Could not find CSPR file for endonuclease {endonuclease}")
184
+ else:
185
+ raise ValueError(f"Could not find organism {org_name} in database")
186
+
187
+ # Generate library using processed targets
188
+ success = self.model.generate_library(
189
+ self.processed_targets if hasattr(self, 'processed_targets') else self.selected_targets,
190
+ settings
191
+ )
192
+
193
+ if success:
194
+ self.view.show_success("Library generated successfully!")
195
+ self.view.close()
196
+
197
+ except ValueError as e:
198
+ self.logger.error(f"Validation error: {str(e)}")
199
+ self.view.show_error("Invalid Input", str(e))
200
+ except Exception as e:
201
+ self.logger.error(f"Error generating library: {str(e)}")
202
+ self.view.show_error(
203
+ "Error",
204
+ f"An error occurred while generating the library: {str(e)}"
205
+ )
206
+
207
+ def _validate_settings(self, settings):
208
+ """Validate library generation settings"""
209
+ try:
210
+ if not settings['output_file']:
211
+ raise ValueError("Please specify an output file")
212
+
213
+ if settings['target_range_start'] >= settings['target_range_end']:
214
+ raise ValueError("Start range must be less than end range")
215
+
216
+ if settings['target_range_start'] < 0 or settings['target_range_end'] > 100:
217
+ raise ValueError("Target range must be between 0 and 100")
218
+
219
+ if settings['space_between_guides'] < 0:
220
+ raise ValueError("Space between guides must be positive")
221
+
222
+ if settings.get('find_off_targets'):
223
+ max_score = settings.get('max_off_target_score')
224
+ if max_score is None or not 0 < max_score <= 0.5:
225
+ raise ValueError("Maximum off-target score must be between 0 and 0.5")
226
+
227
+ except Exception as e:
228
+ self.logger.error(f"Settings validation error: {str(e)}")
229
+ raise
src/controllers/HomeWindowController.py CHANGED
@@ -1,11 +1,12 @@
1
  import os
2
  from PyQt6 import QtWidgets, QtCore, uic
3
- from PyQt6.QtWidgets import QMainWindow
4
  from views.HomeWindowView import HomeWindowView
5
  from models.HomeWindowModel import HomeWindowModel
6
  from utils.ui import show_error, show_message
7
  from PyQt6.QtCore import QObject
8
  from controllers.FindTargetsController import FindTargetsController
 
9
 
10
  class HomeWindowController:
11
  def __init__(self, global_settings):
@@ -16,41 +17,39 @@ class HomeWindowController:
16
  self.view = HomeWindowView(global_settings)
17
  self.init_ui()
18
  self.setup_connections()
19
- self.global_settings.db_state_updated.connect(self.refresh_data)
20
-
21
- main_window = self.global_settings.main_window
22
-
23
- # Ensure the model data is loaded
24
  self.model.load_data()
25
- # self.find_targets_controller = FindTargetsController(global_settings)
 
 
26
  except Exception as e:
27
  show_error(self.global_settings, "Error initializing HomeWindowController", str(e))
28
 
29
  def init_ui(self):
30
  try:
31
- self.view.push_button_view_targets.setEnabled(False)
32
- self.view.push_button_generate_library.setEnabled(False)
33
  self.load_combo_box_data()
34
- self.view.reset_progress_bar()
35
  except Exception as e:
36
  show_error(self.global_settings, "Error initializing UI in HomeWindowController", str(e))
37
 
38
  def load_combo_box_data(self):
 
39
  try:
40
  self.model.load_data()
41
-
42
  organism_to_endonuclease = self.model.get_organism_to_endonuclease()
43
  annotation_files = self.model.get_annotation_files()
44
-
45
- self.logger.debug(f"Updating Organisms combo box with organisms: {organism_to_endonuclease.keys()} in Main window")
46
- self.view.update_combo_box_organism(list(organism_to_endonuclease.keys()))
47
-
 
48
  self.update_combo_box_endonuclease()
49
-
50
- self.logger.debug(f"Updating Annotation files combo box with annotation files: {annotation_files} in Main window")
51
  self.view.update_combo_box_annotation_files(annotation_files)
 
52
  except Exception as e:
53
- show_error(self.global_settings, "Error loading dropdown data in HomeWindowController", str(e))
54
 
55
  def update_combo_box_endonuclease(self):
56
  selected_organism = self.view.combo_box_organism.currentText()
@@ -73,11 +72,12 @@ class HomeWindowController:
73
  self.view.push_button_ncbi_file_search.clicked.connect(self.open_ncbi_window)
74
 
75
  # grpStep3
76
- self.view.radio_button_feature.clicked.connect(self.toggle_annotation)
77
- self.view.radio_button_position.clicked.connect(self.toggle_annotation)
78
- self.view.push_button_find_targets.clicked.connect(self.gather_settings)
79
- self.view.push_button_view_targets.clicked.connect(self.view_results)
80
- self.view.push_button_generate_library.clicked.connect(self.prep_gen_lib)
 
81
 
82
  # Add connection for annotation file changes
83
  self.view.combo_box_local_annotation_files.currentTextChanged.connect(self._on_annotation_file_changed)
@@ -87,27 +87,99 @@ class HomeWindowController:
87
 
88
  # Event Handlers
89
  def gather_settings(self):
 
90
  try:
91
- # input_data = self.view.get_find_targets_input()
92
- # self.find_targets_controller.find_targets(input_data)
93
- # self.global_settings.main_window.open_new_tab("Find Targets", self.find_targets_controller)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
- self.open_find_targets_module()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  except Exception as e:
97
- show_error(self.global_settings, "Error in find_targets", str(e))
 
98
 
99
- def view_results(self):
100
- # Implementation for viewing results
101
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  def toggle_annotation(self):
104
  # Implementation for toggling annotation
105
  pass
106
 
107
- def prep_gen_lib(self):
108
- # Implementation for preparing gene library
109
- pass
110
-
111
  def open_new_genome_module(self):
112
  try:
113
  main_window = self.global_settings.main_window
@@ -172,33 +244,51 @@ class HomeWindowController:
172
  except Exception as e:
173
  show_error(self.global_settings, "Error in open_ncbi_window() in main", str(e))
174
 
175
- def open_find_targets_module(self):
 
176
  try:
177
- find_targets_controller = self.global_settings.get_find_targets_window()
178
- input_data = self.view.get_find_targets_input()
179
- find_targets_controller.find_targets(input_data)
180
- self.global_settings.main_window.open_new_tab("Find Targets", find_targets_controller)
 
 
 
 
 
 
 
 
 
 
 
181
  except Exception as e:
182
- show_error(self.global_settings, "Error in open_find_targets_module() in Home", str(e))
183
 
184
- def open_view_targets_module(self):
 
 
 
 
 
 
 
185
  try:
186
- view_targets_controller = self.global_settings.get_view_targets_window()
187
- self.global_settings.main_window.open_new_tab("View Targets", view_targets_controller)
 
 
 
 
 
 
 
 
 
 
 
188
  except Exception as e:
189
- show_error(self.global_settings, "Error in open_view_targets_module() in Home", str(e))
190
-
191
- def refresh_data(self, is_valid, message, cspr_files):
192
- self.logger.info(f"Refreshing Home Window data after database state update. Valid: {is_valid}, Message: {message}")
193
- if is_valid:
194
- self.load_combo_box_data()
195
- # If the current tab is not Home, we need to update it when it becomes visible
196
- main_window = self.global_settings.main_window
197
- current_tab_text = main_window.view.tab_widget.tabText(main_window.view.tab_widget.currentIndex())
198
- if current_tab_text != "Home":
199
- main_window.view.tab_widget.currentChanged.connect(self._check_and_update_home_tab)
200
- else:
201
- self.logger.warning(f"Database state update received, but it's not valid. Message: {message}")
202
 
203
  def _check_and_update_home_tab(self, index):
204
  if self.global_settings.main_window.view.tab_widget.tabText(index) == "Home":
@@ -219,3 +309,29 @@ class HomeWindowController:
219
  """Handle changes to the annotation file selection"""
220
  self.global_settings.set_current_annotation_file(new_file)
221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  from PyQt6 import QtWidgets, QtCore, uic
3
+ from PyQt6.QtWidgets import QMainWindow, QMessageBox
4
  from views.HomeWindowView import HomeWindowView
5
  from models.HomeWindowModel import HomeWindowModel
6
  from utils.ui import show_error, show_message
7
  from PyQt6.QtCore import QObject
8
  from controllers.FindTargetsController import FindTargetsController
9
+ from models.DatabaseManager import FileChangeType
10
 
11
  class HomeWindowController:
12
  def __init__(self, global_settings):
 
17
  self.view = HomeWindowView(global_settings)
18
  self.init_ui()
19
  self.setup_connections()
 
 
 
 
 
20
  self.model.load_data()
21
+ self.global_settings.db_manager.db_files_changed.connect(self._handle_db_files_changed)
22
+ self.global_settings.db_manager.db_validation_changed.connect(self._handle_db_validation_changed)
23
+ self.global_settings.db_manager.db_state_changed.connect(self._handle_db_state_changed)
24
  except Exception as e:
25
  show_error(self.global_settings, "Error initializing HomeWindowController", str(e))
26
 
27
  def init_ui(self):
28
  try:
 
 
29
  self.load_combo_box_data()
30
+ self.handle_search_type_change()
31
  except Exception as e:
32
  show_error(self.global_settings, "Error initializing UI in HomeWindowController", str(e))
33
 
34
  def load_combo_box_data(self):
35
+ """Reload all combo box data"""
36
  try:
37
  self.model.load_data()
38
+
39
  organism_to_endonuclease = self.model.get_organism_to_endonuclease()
40
  annotation_files = self.model.get_annotation_files()
41
+
42
+ # Update organisms combo box
43
+ self.view.update_combo_box_organism(sorted(organism_to_endonuclease.keys()))
44
+
45
+ # Update endonuclease combo box
46
  self.update_combo_box_endonuclease()
47
+
48
+ # Update annotation files combo box
49
  self.view.update_combo_box_annotation_files(annotation_files)
50
+
51
  except Exception as e:
52
+ show_error(self.global_settings, "Error loading dropdown data", str(e))
53
 
54
  def update_combo_box_endonuclease(self):
55
  selected_organism = self.view.combo_box_organism.currentText()
 
72
  self.view.push_button_ncbi_file_search.clicked.connect(self.open_ncbi_window)
73
 
74
  # grpStep3
75
+ # self.view.radio_button_feature.clicked.connect(self.toggle_annotation)
76
+ # self.view.radio_button_position.clicked.connect(self.toggle_annotation)
77
+ self.view.radio_button_feature.clicked.connect(self.handle_search_type_change)
78
+ self.view.radio_button_position.clicked.connect(self.handle_search_type_change)
79
+ self.view.radio_button_sequence.clicked.connect(self.handle_search_type_change)
80
+ self.view.push_button_find_view_targets.clicked.connect(self.gather_settings)
81
 
82
  # Add connection for annotation file changes
83
  self.view.combo_box_local_annotation_files.currentTextChanged.connect(self._on_annotation_file_changed)
 
87
 
88
  # Event Handlers
89
  def gather_settings(self):
90
+ """Process input data and direct to appropriate view"""
91
  try:
92
+ input_data = self.view.get_find_targets_input()
93
+
94
+ if input_data['search_type'] == 'sequence':
95
+ sequence = input_data['search_query'].strip()
96
+ if len(sequence) < 100:
97
+ QMessageBox.warning(
98
+ self.view,
99
+ "Sequence Too Short",
100
+ "The sequence given is too small. At least 100 characters are required."
101
+ )
102
+ return
103
+ self.open_view_targets(input_data)
104
+ elif input_data['search_type'] == 'position':
105
+ self.open_view_targets(input_data)
106
+ else:
107
+ self.open_find_targets_module()
108
+
109
+ except Exception as e:
110
+ show_error(self.global_settings, "Error in gather_settings", str(e))
111
 
112
+ def open_view_targets(self, input_data):
113
+ try:
114
+ # Create find targets controller to use its model
115
+ find_targets_controller = self.global_settings.get_find_targets_window()
116
+
117
+ # Get targets using the model
118
+ targets = find_targets_controller.model.find_targets(input_data)
119
+
120
+ if targets:
121
+ self.logger.debug(f"Found {len(targets)} targets")
122
+
123
+ # Close existing View Targets tab if it exists
124
+ main_window = self.global_settings.main_window
125
+ existing_tab = main_window.find_tab_by_title("View Targets")
126
+ if existing_tab:
127
+ tab_index = main_window.view.tab_widget.indexOf(existing_tab)
128
+ main_window._close_tab(tab_index)
129
+ self.logger.debug("Closed existing View Targets tab")
130
+
131
+ # Create view targets controller
132
+ view_targets_controller = self.global_settings.get_view_targets_window()
133
+
134
+ view_targets_controller.load_guides(
135
+ targets, # Pass the targets directly
136
+ input_data['organism'],
137
+ input_data['endonuclease']
138
+ )
139
+
140
+ # Open new view targets tab
141
+ main_window.open_new_tab(
142
+ "View Targets",
143
+ view_targets_controller
144
+ )
145
+
146
+ else:
147
+ QMessageBox.warning(
148
+ self.view,
149
+ "No Targets Found",
150
+ "No targets were found for the specified search."
151
+ )
152
+
153
  except Exception as e:
154
+ self.global_settings.logger.error(f"Error opening view targets directly: {str(e)}")
155
+ show_error(self.global_settings, "Error", f"Could not open view targets: {str(e)}")
156
 
157
+ def open_find_targets_module(self):
158
+ """Open find targets module for non-position searches"""
159
+ try:
160
+ # Close existing Find Targets tab if it exists
161
+ main_window = self.global_settings.main_window
162
+ existing_tab = main_window.find_tab_by_title("Find Targets")
163
+ if existing_tab:
164
+ tab_index = main_window.view.tab_widget.indexOf(existing_tab)
165
+ main_window._close_tab(tab_index)
166
+ self.logger.debug("Closed existing Find Targets tab")
167
+
168
+ # Create new find targets controller and load data
169
+ find_targets_controller = self.global_settings.get_find_targets_window()
170
+ input_data = self.view.get_find_targets_input()
171
+ find_targets_controller.find_targets(input_data)
172
+
173
+ # Open new Find Targets tab
174
+ self.global_settings.main_window.open_new_tab("Find Targets", find_targets_controller)
175
+
176
+ except Exception as e:
177
+ show_error(self.global_settings, "Error in open_find_targets_module() in Home", str(e))
178
 
179
  def toggle_annotation(self):
180
  # Implementation for toggling annotation
181
  pass
182
 
 
 
 
 
183
  def open_new_genome_module(self):
184
  try:
185
  main_window = self.global_settings.main_window
 
244
  except Exception as e:
245
  show_error(self.global_settings, "Error in open_ncbi_window() in main", str(e))
246
 
247
+ def _handle_db_files_changed(self, changes):
248
+ """Handle database file changes"""
249
  try:
250
+ # Reload model data if necessary
251
+ self.model.update_for_file_changes(changes)
252
+
253
+ # Update UI if needed
254
+ if (FileChangeType.CSPR_ADDED in changes or
255
+ FileChangeType.CSPR_REMOVED in changes):
256
+ # Update both organism and endonuclease combo boxes
257
+ organism_to_endonuclease = self.model.get_organism_to_endonuclease()
258
+ self.view.update_combo_box_organism(sorted(organism_to_endonuclease.keys()))
259
+ self.update_combo_box_endonuclease()
260
+
261
+ if (FileChangeType.GBFF_ADDED in changes or
262
+ FileChangeType.GBFF_REMOVED in changes):
263
+ self.view.update_combo_box_annotation_files(self.model.get_annotation_files())
264
+
265
  except Exception as e:
266
+ show_error(self.global_settings, "Error handling database changes", str(e))
267
 
268
+ def _handle_db_validation_changed(self, is_valid, message):
269
+ """Handle database validation state changes"""
270
+ if not is_valid:
271
+ self.view.show_warning("Database Warning", message)
272
+ self._update_validation_state(is_valid)
273
+
274
+ def _handle_db_state_changed(self, is_valid, message, changes):
275
+ """Handle database state changes"""
276
  try:
277
+ if not is_valid:
278
+ show_error(self.global_settings, "Database Warning", message)
279
+ return
280
+
281
+ # Always reload model data when database state changes
282
+ self.model.load_data()
283
+
284
+ # Update all combo boxes
285
+ organism_to_endonuclease = self.model.get_organism_to_endonuclease()
286
+ self.view.update_combo_box_organism(sorted(organism_to_endonuclease.keys()))
287
+ self.update_combo_box_endonuclease()
288
+ self.view.update_combo_box_annotation_files(self.model.get_annotation_files())
289
+
290
  except Exception as e:
291
+ show_error(self.global_settings, "Error handling database state change", str(e))
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  def _check_and_update_home_tab(self, index):
294
  if self.global_settings.main_window.view.tab_widget.tabText(index) == "Home":
 
309
  """Handle changes to the annotation file selection"""
310
  self.global_settings.set_current_annotation_file(new_file)
311
 
312
+ def _update_cspr_related_ui(self):
313
+ # Implementation to update UI elements that depend on CSPR files
314
+ pass
315
+
316
+ def _update_gbff_related_ui(self):
317
+ # Implementation to update UI elements that depend on GBFF files
318
+ pass
319
+
320
+ def _update_validation_state(self, is_valid):
321
+ # Implementation to update UI elements based on validation state
322
+ pass
323
+
324
+ def handle_search_type_change(self):
325
+ """Update UI elements based on search type"""
326
+ try:
327
+ search_type = self.view.get_search_type()
328
+
329
+ # Update button text
330
+ if search_type in ['position', 'sequence']:
331
+ self.view.push_button_find_view_targets.setText("View Targets")
332
+ else: # 'feature'
333
+ self.view.push_button_find_view_targets.setText("Find Targets")
334
+
335
+ except Exception as e:
336
+ self.logger.error(f"Error updating search type UI: {str(e)}")
337
+
src/controllers/MainWindowController.py CHANGED
@@ -11,25 +11,26 @@ from utils.LoggingMixin import LoggingMixin
11
  class MainWindowController(LoggingMixin):
12
  def __init__(self, global_settings):
13
  LoggingMixin.__init__(self)
14
- self.global_settings = global_settings
15
  self.tab_widgets = {
16
  'widgets': {},
17
  'controllers': {}
18
  }
19
  self.startup_controller = None
20
- self.is_first_time_startup = self.global_settings.is_first_time_startup
21
  self.shared_tab_size = QSize(850, 850)
22
  self.startup_size = QSize(750, 550)
23
  self.current_tab = None
 
24
 
25
  try:
26
- self.view = MainWindowView(global_settings)
27
  self._setup_connections()
28
  self._init_ui()
29
- self.global_settings.check_and_emit_first_time_startup()
30
  except Exception as e:
31
  self.log_error("__init__", e)
32
- show_error(self.global_settings, "Error initializing MainWindowController", str(e))
33
 
34
  def _setup_connections(self):
35
  self.log_method_call("_setup_connections")
@@ -44,13 +45,20 @@ class MainWindowController(LoggingMixin):
44
  self.view.close_window_button.clicked.connect(self._close_window)
45
  self.view.minimize_window_button.clicked.connect(self._minimize_window)
46
  self.view.maximize_window_button.clicked.connect(self._maximize_window)
47
- self.view.theme_toggle_button.clicked.connect(self._toggle_theme)
48
 
49
  # Tab bar
50
  self.view.tab_widget.tab_closed.connect(self._on_tab_closed)
51
  self.view.tab_widget.tabCloseRequested.connect(self._close_tab)
 
52
 
53
- self.global_settings.first_time_startup.connect(self._handle_first_time_startup)
 
 
 
 
 
 
 
54
 
55
  def _init_ui(self):
56
  self.log_method_call("_init_ui")
@@ -60,8 +68,8 @@ class MainWindowController(LoggingMixin):
60
  self._open_startup_tab()
61
  return
62
 
63
- db_path = self.global_settings.get_db_path()
64
- is_valid, message = self.global_settings.validate_db_path(db_path)
65
 
66
  if db_path and is_valid:
67
  self.log_info(f"Database path is valid: {db_path}")
@@ -77,11 +85,11 @@ class MainWindowController(LoggingMixin):
77
 
78
  def _open_startup_tab(self):
79
  try:
80
- self.startup_controller = self.global_settings.get_startup_window()
81
  self.open_new_tab("Startup", self.startup_controller)
82
  except Exception as e:
83
  self.log_error("_open_startup_tab", e)
84
- show_error(self.global_settings, "Error opening startup tab", str(e))
85
 
86
  def _switch_to_home_from_startup(self):
87
  self.log_method_call("_switch_to_home_from_startup")
@@ -109,20 +117,20 @@ class MainWindowController(LoggingMixin):
109
  self.log_debug(f"Window centered at {self.view.pos()}")
110
  except Exception as e:
111
  self.log_error("_center_window", e)
112
- show_error(self.global_settings, "Error centering window", str(e))
113
 
114
  def _change_database_directory(self):
115
  try:
116
  new_directory = QtWidgets.QFileDialog.getExistingDirectory(
117
  self.view, "Select Database Directory",
118
- self.global_settings.get_db_path(),
119
  QtWidgets.QFileDialog.Option.ShowDirsOnly
120
  )
121
 
122
  if not new_directory:
123
  return
124
 
125
- is_valid, message = self.global_settings.validate_db_path(new_directory)
126
  if is_valid:
127
  self._process_valid_directory(new_directory)
128
  else:
@@ -130,7 +138,7 @@ class MainWindowController(LoggingMixin):
130
 
131
  except Exception as e:
132
  self.log_error("_change_database_directory", e)
133
- show_error(self.global_settings, "Error changing database directory", str(e))
134
 
135
  def _handle_invalid_directory(self, new_directory, message):
136
  reply = QtWidgets.QMessageBox.question(
@@ -143,16 +151,16 @@ class MainWindowController(LoggingMixin):
143
  )
144
 
145
  if reply == QtWidgets.QMessageBox.StandardButton.Yes:
146
- self.global_settings.save_db_path(new_directory)
147
- self.global_settings.update_db_state()
148
  self.open_new_genome_tab()
149
  else:
150
  show_message("Operation Cancelled", "Database directory change cancelled.")
151
 
152
  def _process_valid_directory(self, new_directory):
153
  try:
154
- self.global_settings.save_db_path(new_directory)
155
- self.global_settings.update_db_state()
156
  show_message("Success", "Database directory changed successfully.")
157
 
158
  if (self.startup_controller and
@@ -160,7 +168,7 @@ class MainWindowController(LoggingMixin):
160
  self._switch_to_home_from_startup()
161
  except Exception as e:
162
  self.log_error("_process_valid_directory", e)
163
- show_error(self.global_settings, "Error processing directory", str(e))
164
 
165
  def _open_ncbi_website(self):
166
  ncbi_page()
@@ -202,12 +210,12 @@ class MainWindowController(LoggingMixin):
202
  def _open_home_tab(self):
203
  """Opens the home tab"""
204
  try:
205
- home_controller = self.global_settings.get_home_window()
206
  self.open_new_tab("Home", home_controller)
207
  self.log_info("Home tab opened successfully")
208
  except Exception as e:
209
  self.log_error("_open_home_tab", e)
210
- show_error(self.global_settings, "Error opening home tab", str(e))
211
 
212
  def open_new_tab(self, title, content):
213
  """Opens a new tab with the given title and content"""
@@ -246,40 +254,69 @@ class MainWindowController(LoggingMixin):
246
 
247
  except Exception as e:
248
  self.log_error("open_new_tab", e)
249
- show_error(self.global_settings, f"Error opening tab '{title}'", str(e))
250
 
251
  def _resize_for_tab(self, title):
252
- if title == "Startup":
253
- # For Startup tab, set fixed size and disable maximize button
254
- self.view.setFixedSize(self.startup_size)
255
- self.view.setWindowFlags(self.view.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
256
- else:
257
- # For all other tabs, use the shared size and allow resizing
258
- self.view.setMinimumSize(QSize(400, 300))
259
- self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
260
- self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
- # Only resize if coming from Startup tab or if no current size is set
263
- if self.current_tab == "Startup" or self.view.size() == self.startup_size:
264
- self.view.resize(self.shared_tab_size)
265
-
266
- # Ensure window flags are updated
267
- self.view.show()
268
-
269
- # Update the current tab
270
- self.current_tab = title
271
 
272
  def _close_tab(self, index):
273
- """
274
- Handle tab closure using CloseableTabWidget
275
- """
276
  if 0 <= index < self.view.tab_widget.count():
277
  title = self.view.tab_widget.tabText(index)
278
 
 
 
 
 
279
  # Let CloseableTabWidget handle the widget cleanup
280
  self.view.tab_widget.closeTab(index)
281
 
282
- # Clean up our references
283
  if title in self.tab_widgets['widgets']:
284
  del self.tab_widgets['widgets'][title]
285
  if title in self.tab_widgets['controllers']:
@@ -302,15 +339,15 @@ class MainWindowController(LoggingMixin):
302
 
303
  def _toggle_theme(self):
304
  try:
305
- self.global_settings.set_theme("dark" if self.global_settings.get_theme() == "light" else "light")
306
  self.view.update_theme_icon()
307
  self.view.apply_theme()
308
  except Exception as e:
309
- show_error(self.global_settings, "Error toggling theme", str(e))
310
 
311
  def show(self):
312
  try:
313
- saved_position = self.global_settings.load_window_position("main_window")
314
  if saved_position:
315
  self.view.move(saved_position)
316
  else:
@@ -319,8 +356,8 @@ class MainWindowController(LoggingMixin):
319
  self.view.show()
320
  self.view.apply_theme()
321
  except Exception as e:
322
- self.global_settings.logger.error(f"Error showing main window: {str(e)}", exc_info=True)
323
- show_error(self.global_settings, "Error showing main window", e)
324
 
325
  def open_new_genome_tab(self):
326
  # Check if the New Genome tab already exists
@@ -377,8 +414,33 @@ class MainWindowController(LoggingMixin):
377
  self._resize_for_tab("Home")
378
 
379
  except Exception as e:
380
- self.logger.error(f"Error in close_new_genome_and_switch_to_home: {str(e)}", exc_info=True)
381
- show_error(self.global_settings, "Error switching to Home tab", str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
 
384
 
 
11
  class MainWindowController(LoggingMixin):
12
  def __init__(self, global_settings):
13
  LoggingMixin.__init__(self)
14
+ self.settings = global_settings
15
  self.tab_widgets = {
16
  'widgets': {},
17
  'controllers': {}
18
  }
19
  self.startup_controller = None
20
+ self.is_first_time_startup = self.settings.is_first_time_startup
21
  self.shared_tab_size = QSize(850, 850)
22
  self.startup_size = QSize(750, 550)
23
  self.current_tab = None
24
+ self.previous_size = None
25
 
26
  try:
27
+ self.view = MainWindowView(self.settings)
28
  self._setup_connections()
29
  self._init_ui()
30
+ self.settings.check_and_emit_first_time_startup()
31
  except Exception as e:
32
  self.log_error("__init__", e)
33
+ show_error(self.settings, "Error initializing MainWindowController", str(e))
34
 
35
  def _setup_connections(self):
36
  self.log_method_call("_setup_connections")
 
45
  self.view.close_window_button.clicked.connect(self._close_window)
46
  self.view.minimize_window_button.clicked.connect(self._minimize_window)
47
  self.view.maximize_window_button.clicked.connect(self._maximize_window)
 
48
 
49
  # Tab bar
50
  self.view.tab_widget.tab_closed.connect(self._on_tab_closed)
51
  self.view.tab_widget.tabCloseRequested.connect(self._close_tab)
52
+ self.view.tab_widget.currentChanged.connect(self._on_current_tab_changed)
53
 
54
+ self.settings.first_time_startup.connect(self._handle_first_time_startup)
55
+
56
+ # Add Button Menu
57
+ self.view.action_new_genome.triggered.connect(self.open_new_genome_tab)
58
+ self.view.action_new_endonuclease.triggered.connect(self.open_new_endonuclease_tab)
59
+
60
+ # Settings Menu
61
+ self.view.action_toggle_theme.triggered.connect(self._toggle_theme)
62
 
63
  def _init_ui(self):
64
  self.log_method_call("_init_ui")
 
68
  self._open_startup_tab()
69
  return
70
 
71
+ db_path = self.settings.get_db_path()
72
+ is_valid, message = self.settings.validate_db_path(db_path)
73
 
74
  if db_path and is_valid:
75
  self.log_info(f"Database path is valid: {db_path}")
 
85
 
86
  def _open_startup_tab(self):
87
  try:
88
+ self.startup_controller = self.settings.get_startup_window()
89
  self.open_new_tab("Startup", self.startup_controller)
90
  except Exception as e:
91
  self.log_error("_open_startup_tab", e)
92
+ show_error(self.settings, "Error opening startup tab", str(e))
93
 
94
  def _switch_to_home_from_startup(self):
95
  self.log_method_call("_switch_to_home_from_startup")
 
117
  self.log_debug(f"Window centered at {self.view.pos()}")
118
  except Exception as e:
119
  self.log_error("_center_window", e)
120
+ show_error(self.settings, "Error centering window", str(e))
121
 
122
  def _change_database_directory(self):
123
  try:
124
  new_directory = QtWidgets.QFileDialog.getExistingDirectory(
125
  self.view, "Select Database Directory",
126
+ self.settings.get_db_path(),
127
  QtWidgets.QFileDialog.Option.ShowDirsOnly
128
  )
129
 
130
  if not new_directory:
131
  return
132
 
133
+ is_valid, message = self.settings.validate_db_path(new_directory)
134
  if is_valid:
135
  self._process_valid_directory(new_directory)
136
  else:
 
138
 
139
  except Exception as e:
140
  self.log_error("_change_database_directory", e)
141
+ show_error(self.settings, "Error changing database directory", str(e))
142
 
143
  def _handle_invalid_directory(self, new_directory, message):
144
  reply = QtWidgets.QMessageBox.question(
 
151
  )
152
 
153
  if reply == QtWidgets.QMessageBox.StandardButton.Yes:
154
+ self.settings.save_db_path(new_directory)
155
+ self.settings.update_db_state()
156
  self.open_new_genome_tab()
157
  else:
158
  show_message("Operation Cancelled", "Database directory change cancelled.")
159
 
160
  def _process_valid_directory(self, new_directory):
161
  try:
162
+ self.settings.save_db_path(new_directory)
163
+ self.settings.update_db_state()
164
  show_message("Success", "Database directory changed successfully.")
165
 
166
  if (self.startup_controller and
 
168
  self._switch_to_home_from_startup()
169
  except Exception as e:
170
  self.log_error("_process_valid_directory", e)
171
+ show_error(self.settings, "Error processing directory", str(e))
172
 
173
  def _open_ncbi_website(self):
174
  ncbi_page()
 
210
  def _open_home_tab(self):
211
  """Opens the home tab"""
212
  try:
213
+ home_controller = self.settings.get_home_window()
214
  self.open_new_tab("Home", home_controller)
215
  self.log_info("Home tab opened successfully")
216
  except Exception as e:
217
  self.log_error("_open_home_tab", e)
218
+ show_error(self.settings, "Error opening home tab", str(e))
219
 
220
  def open_new_tab(self, title, content):
221
  """Opens a new tab with the given title and content"""
 
254
 
255
  except Exception as e:
256
  self.log_error("open_new_tab", e)
257
+ show_error(self.settings, f"Error opening tab '{title}'", str(e))
258
 
259
  def _resize_for_tab(self, title):
260
+ try:
261
+ if title == "Startup":
262
+ # For Startup tab, set fixed size and disable maximize button
263
+ self.view.setFixedSize(self.startup_size)
264
+ self.view.setWindowFlags(self.view.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
265
+ elif title in ["View Targets", "Multitargeting Analysis"]:
266
+ # Store current size before applying constraints
267
+ if self.current_tab not in ["View Targets", "Multitargeting Analysis"]:
268
+ self.previous_size = self.view.size()
269
+
270
+ # Set minimum dimensions for these tabs
271
+ min_width = 1300
272
+ min_height = 800
273
+
274
+ # Calculate new dimensions
275
+ new_width = max(self.view.width(), min_width)
276
+ new_height = max(self.view.height(), min_height)
277
+
278
+ # Only resize if dimensions need to increase
279
+ if new_width > self.view.width() or new_height > self.view.height():
280
+ self.view.resize(QSize(new_width, new_height))
281
+
282
+ # Set minimum size constraints
283
+ self.view.setMinimumSize(QSize(min_width, min_height))
284
+ self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
285
+ self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
286
+ else:
287
+ # For all other tabs
288
+ self.view.setMinimumSize(QSize(400, 300))
289
+ self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
290
+ self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
291
+
292
+ # Restore previous size if available and coming from View Targets or Multi-targeting Analysis
293
+ if self.current_tab in ["View Targets", "Multitargeting Analysis"] and self.previous_size:
294
+ self.view.resize(self.previous_size)
295
+ elif self.current_tab == "Startup" or self.view.size() == self.startup_size:
296
+ self.view.resize(self.shared_tab_size)
297
 
298
+ # Ensure window flags are updated
299
+ self.view.show()
300
+
301
+ # Update the current tab
302
+ self.current_tab = title
303
+
304
+ except Exception as e:
305
+ self.log_error("_resize_for_tab", e)
 
306
 
307
  def _close_tab(self, index):
308
+ """Handle tab closure using CloseableTabWidget"""
 
 
309
  if 0 <= index < self.view.tab_widget.count():
310
  title = self.view.tab_widget.tabText(index)
311
 
312
+ # Store size before closing View Targets tab
313
+ if title == "View Targets":
314
+ self.previous_size = self.view.size()
315
+
316
  # Let CloseableTabWidget handle the widget cleanup
317
  self.view.tab_widget.closeTab(index)
318
 
319
+ # Clean up references
320
  if title in self.tab_widgets['widgets']:
321
  del self.tab_widgets['widgets'][title]
322
  if title in self.tab_widgets['controllers']:
 
339
 
340
  def _toggle_theme(self):
341
  try:
342
+ self.settings.set_theme("dark" if self.settings.get_theme() == "light" else "light")
343
  self.view.update_theme_icon()
344
  self.view.apply_theme()
345
  except Exception as e:
346
+ show_error(self.settings, "Error toggling theme", str(e))
347
 
348
  def show(self):
349
  try:
350
+ saved_position = self.settings.load_window_position("main_window")
351
  if saved_position:
352
  self.view.move(saved_position)
353
  else:
 
356
  self.view.show()
357
  self.view.apply_theme()
358
  except Exception as e:
359
+ self.log_error("show", e)
360
+ show_error(self.settings, "Error showing main window", e)
361
 
362
  def open_new_genome_tab(self):
363
  # Check if the New Genome tab already exists
 
414
  self._resize_for_tab("Home")
415
 
416
  except Exception as e:
417
+ self.log_error("close_new_genome_and_switch_to_home", e)
418
+ show_error(self.settings, "Error switching to Home tab", str(e))
419
+
420
+ def _on_current_tab_changed(self, index):
421
+ """Handle tab change events"""
422
+ try:
423
+ if index >= 0:
424
+ new_tab_title = self.view.tab_widget.tabText(index)
425
+ old_tab_title = self.current_tab
426
+
427
+ # Store current size if coming from a non-Startup tab
428
+ if old_tab_title and old_tab_title != "Startup":
429
+ self.previous_size = self.view.size()
430
+
431
+ self._resize_for_tab(new_tab_title)
432
+
433
+ except Exception as e:
434
+ self.log_error("_on_current_tab_changed", e)
435
+
436
+ def open_new_endonuclease_tab(self):
437
+ """Opens the new endonuclease window"""
438
+ try:
439
+ new_endonuclease_controller = self.settings.get_new_endonuclease_window()
440
+ new_endonuclease_controller.view.show() # Show as window instead of tab
441
+ except Exception as e:
442
+ self.log_error("open_new_endonuclease_tab", e)
443
+ show_error(self.settings, "Error opening new endonuclease window", str(e))
444
 
445
 
446
 
src/controllers/MultitargetingWindowController.py CHANGED
@@ -1,7 +1,7 @@
1
  from PyQt6.QtWidgets import QMainWindow
2
  from views.MultitargetingWindowView import MultitargetingWindowView
3
  from models.MultitargetingWindowModel import MultitargetingWindowModel
4
- from utils.ui import show_error
5
 
6
  class MultitargetingWindowController(QMainWindow):
7
  def __init__(self, global_settings):
@@ -35,6 +35,9 @@ class MultitargetingWindowController(QMainWindow):
35
  # Initialize plots
36
  self._view.setup_plots()
37
 
 
 
 
38
  except Exception as e:
39
  self.logger.error(f"Error in _init_ui: {str(e)}")
40
  show_error(self.settings, "Error", f"Failed to initialize UI: {str(e)}")
@@ -47,13 +50,15 @@ class MultitargetingWindowController(QMainWindow):
47
 
48
  # Buttons
49
  self._view.push_button_analyze.clicked.connect(self._on_analyze_clicked)
50
- self._view.push_button_statistics_overview.clicked.connect(self._on_statistics_overview_clicked)
51
- self._view.tool_button_sql_settings.clicked.connect(self._on_sql_settings_clicked)
52
 
53
  # Table selection
54
  self._view.table_seeds.itemSelectionChanged.connect(self._on_seed_selected)
55
  self._view.check_box_select_all.stateChanged.connect(self._on_select_all_changed)
56
 
 
 
57
  def _on_organism_changed(self, index):
58
  """Handle organism selection change"""
59
  try:
@@ -215,7 +220,7 @@ class MultitargetingWindowController(QMainWindow):
215
 
216
  def _update_analysis_button_state(self):
217
  """Update analyze button enabled state"""
218
- has_organism = bool(self._view.organism_drop.currentText())
219
  has_endo = bool(self._view.combo_box_endonuclease.currentText())
220
  self._view.push_button_analyze.setEnabled(has_organism and has_endo)
221
 
@@ -225,14 +230,22 @@ class MultitargetingWindowController(QMainWindow):
225
  self._view.update_plots(None, None, None)
226
 
227
  def _update_plots(self):
228
- """Update all plots with current data"""
229
  try:
230
- # Get repeats vs seeds data first
231
  repeats_data = self._model.get_repeats_vs_seeds_data()
232
-
233
- # Get sequences vs repeats data
234
  sequences_data = self._model.get_seeds_vs_repeats_data()
235
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  # Update all plots at once
237
  self._view.update_plots(repeats_data, sequences_data, None) # chromosome_data will be updated on seed selection
238
 
@@ -254,3 +267,61 @@ class MultitargetingWindowController(QMainWindow):
254
  """Get settings from SQL settings dialog"""
255
  # Implement getting settings from dialog
256
  return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from PyQt6.QtWidgets import QMainWindow
2
  from views.MultitargetingWindowView import MultitargetingWindowView
3
  from models.MultitargetingWindowModel import MultitargetingWindowModel
4
+ from utils.ui import show_error, show_message
5
 
6
  class MultitargetingWindowController(QMainWindow):
7
  def __init__(self, global_settings):
 
35
  # Initialize plots
36
  self._view.setup_plots()
37
 
38
+ # Connect max results line edit
39
+ self._view.line_edit_max_results.textChanged.connect(self._on_max_results_changed)
40
+
41
  except Exception as e:
42
  self.logger.error(f"Error in _init_ui: {str(e)}")
43
  show_error(self.settings, "Error", f"Failed to initialize UI: {str(e)}")
 
50
 
51
  # Buttons
52
  self._view.push_button_analyze.clicked.connect(self._on_analyze_clicked)
53
+ # self._view.push_button_statistics_overview.clicked.connect(self._on_statistics_overview_clicked)
54
+ # self._view.tool_button_sql_settings.clicked.connect(self._on_sql_settings_clicked)
55
 
56
  # Table selection
57
  self._view.table_seeds.itemSelectionChanged.connect(self._on_seed_selected)
58
  self._view.check_box_select_all.stateChanged.connect(self._on_select_all_changed)
59
 
60
+ self._view.push_button_export_selected_gRNAs.clicked.connect(self._handle_export)
61
+
62
  def _on_organism_changed(self, index):
63
  """Handle organism selection change"""
64
  try:
 
220
 
221
  def _update_analysis_button_state(self):
222
  """Update analyze button enabled state"""
223
+ has_organism = bool(self._view.combo_box_organism.currentText())
224
  has_endo = bool(self._view.combo_box_endonuclease.currentText())
225
  self._view.push_button_analyze.setEnabled(has_organism and has_endo)
226
 
 
230
  self._view.update_plots(None, None, None)
231
 
232
  def _update_plots(self):
 
233
  try:
 
234
  repeats_data = self._model.get_repeats_vs_seeds_data()
 
 
235
  sequences_data = self._model.get_seeds_vs_repeats_data()
236
 
237
+ # Get statistics for overview tab
238
+ stats = self._model.calculate_statistics()
239
+
240
+ # Update statistics labels
241
+ if stats:
242
+ self._view.update_statistics_labels(
243
+ total_repeats=stats.get('repeat_count', 0),
244
+ avg_repeats=stats.get('average', 0),
245
+ median_repeats=stats.get('median', 0),
246
+ mode_repeats=stats.get('mode', 0)
247
+ )
248
+
249
  # Update all plots at once
250
  self._view.update_plots(repeats_data, sequences_data, None) # chromosome_data will be updated on seed selection
251
 
 
267
  """Get settings from SQL settings dialog"""
268
  # Implement getting settings from dialog
269
  return {}
270
+
271
+ def _on_max_results_changed(self, value):
272
+ """Handle changes to max results setting"""
273
+ try:
274
+ if value == "": # Handle empty input
275
+ self._model.set_row_limit(1000) # Reset to default
276
+ return
277
+
278
+ # Convert to int and update model
279
+ limit = int(value)
280
+ if limit <= 0: # Handle negative or zero values
281
+ limit = -1 # Use -1 to indicate no limit
282
+ self._model.set_row_limit(limit)
283
+
284
+ except ValueError:
285
+ # Reset to default if invalid input
286
+ self._model.set_row_limit(1000)
287
+ self._view.line_edit_max_results.setText("1000")
288
+
289
+ def _handle_export(self):
290
+ """Handle export button click"""
291
+ try:
292
+ selected_items = []
293
+ selected_rows = self._view.table_seeds.selectedItems()
294
+
295
+ if not selected_rows:
296
+ show_message(
297
+ "Warning",
298
+ "Please select at least one row to export."
299
+ )
300
+ return
301
+
302
+ # Get unique rows (since selecting one row selects all its columns)
303
+ selected_row_numbers = set()
304
+ for item in selected_rows:
305
+ selected_row_numbers.add(item.row())
306
+
307
+ # For each selected row, create a dictionary with the row data
308
+ for row in selected_row_numbers:
309
+ item_data = {
310
+ 'seed': self._view.table_seeds.item(row, 0).text(),
311
+ 'total_repeats': self._view.table_seeds.item(row, 1).text(),
312
+ 'avg_repeats_or_scaffold': self._view.table_seeds.item(row, 2).text(),
313
+ 'consensus_sequence': self._view.table_seeds.item(row, 3).text(),
314
+ 'percent_consensus': self._view.table_seeds.item(row, 4).text(),
315
+ 'score': self._view.table_seeds.item(row, 5).text(),
316
+ 'pam': self._view.table_seeds.item(row, 6).text(),
317
+ 'strand': self._view.table_seeds.item(row, 7).text()
318
+ }
319
+ selected_items.append(item_data)
320
+
321
+ # Get export window controller and show dialog
322
+ export_controller = self.settings.get_export_selected_grnas_window()
323
+ export_controller.show_dialog(selected_items, "Multitargeting")
324
+
325
+ except Exception as e:
326
+ self.logger.error(f"Error handling export: {str(e)}")
327
+ show_error(self.settings, "Export Error", str(e))
src/controllers/NCBIWindowController.py CHANGED
@@ -1,5 +1,5 @@
1
  from PyQt6 import QtWidgets, QtCore, QtGui
2
- from utils.ui import show_error, show_message, position_window
3
  from models.NCBIWindowModel import NCBIWindowModel, PandasModel, CustomProxyModel
4
  from views.NCBIWindowView import NCBIWindowView
5
  import os
 
1
  from PyQt6 import QtWidgets, QtCore, QtGui
2
+ from utils.ui import show_error, show_message
3
  from models.NCBIWindowModel import NCBIWindowModel, PandasModel, CustomProxyModel
4
  from views.NCBIWindowView import NCBIWindowView
5
  import os
src/controllers/NewEndonucleaseController.py CHANGED
@@ -12,6 +12,7 @@ class NewEndonucleaseController:
12
  self.model = NewEndonucleaseModel(self.settings)
13
  self.view = NewEndonucleaseView(self.settings)
14
  self.model.endonuclease_updated.connect(self._on_endonuclease_updated)
 
15
 
16
  self._setup_connections()
17
  self._init_ui()
 
12
  self.model = NewEndonucleaseModel(self.settings)
13
  self.view = NewEndonucleaseView(self.settings)
14
  self.model.endonuclease_updated.connect(self._on_endonuclease_updated)
15
+ self.settings.theme_changed.connect(self.view.apply_theme)
16
 
17
  self._setup_connections()
18
  self._init_ui()
src/controllers/OffTarget.py DELETED
@@ -1,277 +0,0 @@
1
- import os, platform
2
- from PyQt5 import QtWidgets, uic, QtCore, QtGui, Qt
3
- from functools import partial
4
- import models.GlobalSettings as GlobalSettings
5
- from utils.ui import show_message, show_error, scale_ui, center_ui
6
-
7
- logger = GlobalSettings.logger
8
-
9
- class OffTarget(QtWidgets.QMainWindow):
10
- def __init__(self):
11
- try:
12
- super(OffTarget, self).__init__()
13
- uic.loadUi(GlobalSettings.appdir + 'ui/off_target.ui', self)
14
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
15
- self.setWindowTitle("Off-Target Analysis")
16
- self.progressBar.setMinimum(0)
17
- self.progressBar.setMaximum(100)
18
- self.progressBar.setValue(0)
19
- self.run_clicked = False
20
- self.Run.clicked.connect(self.run_analysis)
21
- # self.tolerancehorizontalSlider.valueChanged.connect(self.tol_change)
22
- # self.tolerancehorizontalSlider.setMaximum(100)
23
- # self.tolerancehorizontalSlider.setMinimum(0)
24
- self.tolerance = 0.0
25
-
26
- self.cancelButton.clicked.connect(self.exit)
27
- self.fill_data_dropdown()
28
- self.perc = False
29
- self.bool_temp = False
30
- self.running = False
31
- self.process = QtCore.QProcess()
32
- self.output_path = ''
33
-
34
- groupbox_style = """
35
- QGroupBox:title{subcontrol-origin: margin;
36
- left: 10px;
37
- padding: 0 5px 0 5px;}
38
- QGroupBox#Step1{border: 2px solid rgb(111,181,110);
39
- border-radius: 9px;
40
- font: bold 14pt 'Arial';
41
- margin-top: 10px;}"""
42
-
43
- self.Step1.setStyleSheet(groupbox_style)
44
- self.Step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
45
- self.Step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
46
-
47
- scale_ui(self, custom_scale_width=400, custom_scale_height=450)
48
-
49
- except Exception as e:
50
- show_error("Error initializing OffTarget class.", e)
51
-
52
- #copied from MT to fill in the chromo and endo dropdowns based on CSPR files user provided at the startup
53
- def fill_data_dropdown(self):
54
- try:
55
- try:
56
- self.EndocomboBox.diconnect()
57
- except:
58
- pass
59
- try:
60
- self.OrgcomboBox.diconnect()
61
- except:
62
- pass
63
-
64
- self.OrgcomboBox.clear()
65
- self.EndocomboBox.clear()
66
- self.mismatchcomboBox.clear()
67
-
68
- self.organisms_to_files = {}
69
- self.organisms_to_endos = {}
70
-
71
- #fill in chromosome and endo dropdowns
72
- onlyfiles = [f for f in os.listdir(GlobalSettings.CSPR_DB) if os.path.isfile(os.path.join(GlobalSettings.CSPR_DB , f))]
73
- self.orgsandendos = {}
74
- self.shortName = {}
75
- for file in onlyfiles:
76
- if file.find('.cspr') != -1:
77
- newname = file[0:-4]
78
- endo = newname[newname.rfind("_") + 1:-1]
79
- hold = open(file, 'r')
80
- buf = (hold.readline())
81
- hold.close()
82
- buf = str(buf)
83
- buf = buf.strip()
84
- species = buf.replace("GENOME: ", "")
85
-
86
- if species in self.organisms_to_files:
87
- self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
88
- else:
89
- self.organisms_to_files[species] = {}
90
- self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
91
-
92
- if species in self.organisms_to_endos:
93
- self.organisms_to_endos[species].append(endo)
94
- else:
95
- self.organisms_to_endos[species] = [endo]
96
- if self.OrgcomboBox.findText(species) == -1:
97
- self.OrgcomboBox.addItem(species)
98
-
99
- # fill in endos dropdown based on current organism
100
- endos = self.organisms_to_endos[str(self.OrgcomboBox.currentText())]
101
- self.EndocomboBox.addItems(endos)
102
- self.OrgcomboBox.currentIndexChanged.connect(self.update_endos)
103
- self.EndocomboBox.currentIndexChanged.connect(self.change_endos)
104
-
105
- # update file names for current org/endo combo
106
- self.cspr_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][0]
107
- self.db_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][1]
108
-
109
- #fill in Max Mismatch dropdown
110
- mismatch_list = ['1','2','3','4','5','6','7','8','9','10']
111
- self.mismatchcomboBox.addItems(mismatch_list)
112
- self.mismatchcomboBox.setCurrentIndex(3) ### Max number of mismatches is 4 by default
113
- except Exception as e:
114
- show_error("Error in fill_data_dropdown() in OffTarget.", e)
115
-
116
- def change_endos(self):
117
- try:
118
- #update file names based on current org/endo combo
119
- self.cspr_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][str(self.EndocomboBox.currentText())][0]
120
- self.db_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][str(self.EndocomboBox.currentText())][1]
121
- except Exception as e:
122
- show_error("Error in change_endos() in OffTarget.", e)
123
-
124
- def update_endos(self):
125
- try:
126
- #try to disconnect index changed signal on endo dropdown if there is one
127
- try:
128
- self.EndocomboBox.currentIndexChanged.disconnect()
129
- except:
130
- pass
131
-
132
- #clear endo dropdown and fill in with endos relative to the current organism
133
- self.EndocomboBox.clear()
134
- endos = self.organisms_to_endos[str(self.OrgcomboBox.currentText())]
135
- self.EndocomboBox.addItems(endos)
136
- self.cspr_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][0]
137
- self.db_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][1]
138
-
139
- #reconnect index changed signal on endo dropdown
140
- self.EndocomboBox.currentIndexChanged.connect(self.change_endos)
141
- except Exception as e:
142
- show_error("Error in update_endos() in OffTarget.", e)
143
-
144
- #tolerance slider / entry box. Allows for slider to update, or the user to input in text box
145
- # def tol_change(self):
146
- # try:
147
- # if(self.tolerance == float(self.tolerancelineEdit.text())):
148
- # self.tolerance = self.tolerancehorizontalSlider.value() / 100 * 0.5
149
- # self.tolerance = round(self.tolerance, 3)
150
- # self.tolerancelineEdit.setText(str(self.tolerance))
151
- # else:
152
- # self.tolerance = float(self.tolerancelineEdit.text())
153
- # self.tolerance = round(self.tolerance, 3)
154
- # self.tolerancehorizontalSlider.setValue(round(self.tolerance/0.5 * 100))
155
- # except Exception as e:
156
- # logger.critical("Error in tol_change() in OffTarget.")
157
- # logger.critical(e)
158
- # logger.critical(traceback.format_exc())
159
- # exit(-1)
160
-
161
- #run button linked to run_analysis, which is linked to the run button
162
- def run_command(self):
163
- try:
164
- output_file_name = self.outputFileName.text().strip()
165
- save_internally = self.outputCheckBox.isChecked()
166
- if not output_file_name and not save_internally:
167
- show_message(
168
- fontSize=self.fontSize,
169
- icon=QtWidgets.QMessageBox.Icon.Warning,
170
- title="Input Error",
171
- message="Please enter a valid output file name."
172
- )
173
- return
174
-
175
- if save_internally:
176
- full_output_path = os.path.join(GlobalSettings.appdir, 'local/local_output.txt')
177
- else:
178
- full_output_path = os.path.join(GlobalSettings.CSPR_DB, output_file_name)
179
- if os.path.isfile(full_output_path):
180
- show_message(
181
- fontSize=self.fontSize,
182
- icon=QtWidgets.QMessageBox.Icon.Critical,
183
- title="Error",
184
- message="Output file already exists. Please choose a new output file name."
185
- )
186
- return
187
-
188
- self.tolerance = self.toleranceSpinBox.value()
189
- self.perc = False
190
- self.bool_temp = False
191
- self.running = False
192
-
193
- app_path = GlobalSettings.appdir.replace('\\','/')
194
- exe_path = os.path.join(app_path, 'OffTargetFolder', 'OT_Win.exe' if platform.system() == 'Windows' else 'OT_Lin' if platform.system() == 'Linux' else 'OT_Mac')
195
- data_path = os.path.join(app_path, 'OffTargetFolder', 'temp.txt')
196
- endo = self.EndocomboBox.currentText()
197
- cspr_path = os.path.join(GlobalSettings.CSPR_DB, self.cspr_file)
198
- db_path = os.path.join(GlobalSettings.CSPR_DB, self.db_file)
199
- CASPER_info_path = os.path.join(app_path, 'CASPERinfo')
200
- num_of_mismatches = int(self.mismatchcomboBox.currentText())
201
- hsu = GlobalSettings.mainWindow.Results.endo_data[self.EndocomboBox.currentText()][2]
202
-
203
- self.output_path = ' "' + full_output_path + '"'
204
-
205
- cmd = f'"{exe_path}" "{data_path}" "{endo}" "{cspr_path}" "{db_path}" "{full_output_path}" "{CASPER_info_path}" {num_of_mismatches} {self.tolerance} {"TRUE" if self.AVG.isChecked() else "FALSE"} {"FALSE" if self.AVG.isChecked() else "TRUE"} "{hsu}"'
206
- cmd = cmd.replace('/', '\\') if platform.system() == 'Windows' else cmd
207
-
208
- def finished():
209
- self.running = False
210
- self.run_clicked = True
211
- self.progressBar.setValue(100)
212
-
213
- #used to know when data is ready to read from stdout
214
- def dataReady():
215
- #filter the data from stdout, bools used to know when the .exe starts outputting the progress
216
- #percentages to be able to type cast them as floats and update the progress bar. Also, must
217
- #split the input read based on '\n\ characters since the stdout read can read multiple lines at
218
- #once and is all read in as raw bytes
219
- raw_data = self.process.readAllStandardOutput().data().decode()
220
- lines = raw_data.replace('\r', '').split('\n')
221
- for line in lines:
222
- if "Parsing Input Arguments" in line:
223
- self.progressBar.setValue(10)
224
- elif "Loading data for algorithm" in line:
225
- self.progressBar.setValue(25)
226
- elif "Running OffTarget Analysis" in line:
227
- self.progressBar.setValue(50)
228
-
229
- #connect QProcess to the dataReady func, and finished func, reset progressBar only if the outputfile name
230
- #given does not already exist
231
- self.process.readyReadStandardOutput.connect(partial(dataReady))
232
- self.process.readyReadStandardError.connect(partial(dataReady))
233
- self.progressBar.setValue(1)
234
- QtCore.QTimer.singleShot(100, partial(self.process.start, cmd))
235
- self.process.finished.connect(finished)
236
- except Exception as e:
237
- show_error("Error in run_command() in OffTarget.", e)
238
-
239
- def run_analysis(self):
240
- try:
241
- #make sure an analysis isn't already running before starting
242
- if(self.running == False):
243
- self.running = True
244
- self.run_command()
245
- except Exception as e:
246
- show_error("Error in run_analysis() in OffTarget.", e)
247
-
248
- #exit linked to user clicking cancel, resets bools, and kills process if one was running
249
- def exit(self):
250
- try:
251
- self.perc = False
252
- self.bool_temp = False
253
- self.running = False
254
- self.process.kill()
255
- self.hide()
256
-
257
- local_output_path = os.path.join(GlobalSettings.appdir, 'local/local_output.txt')
258
-
259
- if os.path.exists(local_output_path):
260
- os.remove(local_output_path)
261
- print("Local output file deleted successfully.")
262
- else:
263
- print("Local output file does not exist.")
264
- except Exception as e:
265
- show_error("Error in exit() in OffTarget.", e)
266
-
267
- #closeEvent linked to user pressing the x in the top right of windows, resets bools, and
268
- #kills process if there was one running
269
- def closeEvent(self, event):
270
- try:
271
- self.process.kill()
272
- self.perc = False
273
- self.bool_temp = False
274
- self.running = False
275
- event.accept()
276
- except Exception as e:
277
- show_error("Error in closeEvent() in OffTarget.", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/controllers/OffTargetController.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from models.OffTargetModel import OffTargetModel
2
+ from utils.ui import show_error
3
+ from views.OffTargetView import OffTargetView
4
+ from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal
5
+ import os
6
+
7
+ class OffTargetController(QObject):
8
+ # Update signal to emit tuple of (scores, details)
9
+ off_target_results_ready = pyqtSignal(tuple) # Emits (scores_dict, details_dict)
10
+
11
+ def __init__(self, global_settings):
12
+ super().__init__()
13
+ self.global_settings = global_settings
14
+ self.logger = global_settings.get_logger()
15
+
16
+ try:
17
+ self.model = OffTargetModel(global_settings)
18
+ self.view = OffTargetView(global_settings)
19
+
20
+ self._init_ui()
21
+
22
+ self._setup_connections()
23
+
24
+ self.global_settings.theme_changed.connect(self.on_theme_changed)
25
+ except Exception as e:
26
+ show_error(self.global_settings, "Error initializing OffTargetController", str(e))
27
+
28
+ def _init_ui(self):
29
+ try:
30
+ organisms = self.model.get_organisms()
31
+ self.view.set_combo_box_organism(organisms)
32
+
33
+ if organisms:
34
+ endonucleases = self.model.get_endonucleases(organisms[0])
35
+ self.view.set_combo_box_endonuclease(endonucleases)
36
+
37
+ self.view.set_combo_box_max_mismatches()
38
+
39
+ except Exception as e:
40
+ self.logger.error(f"Error setting up initial data: {str(e)}")
41
+ self.view.show_error("Setup Error", str(e))
42
+
43
+ def _setup_connections(self):
44
+ try:
45
+ self.view.push_button_submit.clicked.connect(self._on_submit_clicked)
46
+
47
+ # Connect to model's signals
48
+ self.model.results_ready.connect(lambda results: self._on_results_ready(results))
49
+ self.model.progress_updated.connect(self._on_progress_updated)
50
+
51
+ except Exception as e:
52
+ self.logger.error(f"Error connecting signals: {str(e)}")
53
+
54
+ @pyqtSlot(str)
55
+ def _on_organism_changed(self, organism):
56
+ """Handle organism selection change"""
57
+ try:
58
+ endonucleases = self.model.get_endonucleases(organism)
59
+ self.view.set_endonucleases(endonucleases)
60
+ except Exception as e:
61
+ self.logger.error(f"Error updating endonucleases: {str(e)}")
62
+
63
+ @pyqtSlot()
64
+ def _on_submit_clicked(self):
65
+ """Handle submit button click"""
66
+ try:
67
+ # Get analysis parameters
68
+ parameters = self.view.get_analysis_parameters()
69
+
70
+ # Add stored targets
71
+ if hasattr(self, '_targets'):
72
+ parameters['targets'] = self._targets
73
+ else:
74
+ raise ValueError("No targets available for analysis")
75
+
76
+ # Validate parameters
77
+ if not self._validate_parameters(parameters):
78
+ return
79
+
80
+ self.model._write_targets_to_temp(parameters['targets'])
81
+
82
+ # Start analysis
83
+ self.model.start_analysis(parameters)
84
+
85
+ # Update UI
86
+ self.view.update_progress_bar(1, "Starting analysis...")
87
+ self.view.push_button_submit.setEnabled(False)
88
+
89
+ except Exception as e:
90
+ self.logger.error(f"Error in submit action: {str(e)}")
91
+ self.view.show_error("Submit Error", str(e))
92
+
93
+ def _validate_parameters(self, parameters):
94
+ """Validate analysis parameters"""
95
+ try:
96
+ if parameters['save_output']:
97
+ if not parameters['output_filename']:
98
+ self.view.show_warning(
99
+ "Input Error",
100
+ "Please enter a valid output file name."
101
+ )
102
+ return False
103
+
104
+ output_path = os.path.join(
105
+ self.global_settings.get_db_path(),
106
+ parameters['output_filename']
107
+ )
108
+ if os.path.exists(output_path):
109
+ self.view.show_warning(
110
+ "File Error",
111
+ "Output file already exists. Please choose a new name."
112
+ )
113
+ return False
114
+
115
+ return True
116
+
117
+ except Exception as e:
118
+ self.logger.error(f"Error validating parameters: {str(e)}")
119
+ return False
120
+
121
+ @pyqtSlot()
122
+ def _on_cancel_clicked(self):
123
+ """Handle cancel button click"""
124
+ try:
125
+ self.model.stop_analysis()
126
+ self._cleanup_temp_files()
127
+ self.view.close()
128
+ except Exception as e:
129
+ self.logger.error(f"Error canceling analysis: {str(e)}")
130
+
131
+ def _cleanup_temp_files(self):
132
+ """Clean up temporary files"""
133
+ try:
134
+ # Get path to temp file
135
+ temp_path = os.path.join(
136
+ self.global_settings.get_off_target_dir_path(),
137
+ 'temp.txt'
138
+ )
139
+
140
+ # Clean up temp file
141
+ if os.path.exists(temp_path):
142
+ os.remove(temp_path)
143
+ self.logger.debug(f"Removed temp file: {temp_path}")
144
+
145
+ # Clean up local output file
146
+ local_output = os.path.join(
147
+ self.global_settings.get_off_target_dir_path(),
148
+ 'local_output.txt'
149
+ )
150
+ if os.path.exists(local_output):
151
+ os.remove(local_output)
152
+ self.logger.debug(f"Removed local output file: {local_output}")
153
+
154
+ except Exception as e:
155
+ self.logger.error(f"Error cleaning up temp files: {str(e)}")
156
+
157
+ def _on_results_ready(self, results):
158
+ """Handle results from model"""
159
+ try:
160
+ scores, details = results # Unpack the tuple of results
161
+ if scores: # Make sure we have valid results
162
+ # Store results
163
+ self._off_target_results = scores
164
+ self._off_target_details = details
165
+
166
+ # Emit results signal immediately with both scores and details
167
+ self.off_target_results_ready.emit((scores, details))
168
+
169
+ # Close the window
170
+ self.view.close()
171
+
172
+ self.logger.debug(f"Received and emitted {len(scores)} off-target results with {len(details)} detailed results")
173
+
174
+ else:
175
+ self.logger.warning("Received empty results")
176
+ self.view.show_warning(
177
+ "No Results",
178
+ "No off-target analysis results were generated."
179
+ )
180
+
181
+ except Exception as e:
182
+ self.logger.error(f"Error handling results: {str(e)}")
183
+ self.view.show_error("Results Error", str(e))
184
+
185
+ def show(self):
186
+ """Show the view and bring to front"""
187
+ # Reset view state before showing
188
+ self.view.prog_bar.setValue(0)
189
+ self.view.push_button_submit.setEnabled(True)
190
+
191
+ # Show and bring window to front
192
+ self.view.show()
193
+ self.view.raise_() # Bring window to front
194
+ self.view.activateWindow() # Give window focus
195
+ self.view.apply_theme()
196
+
197
+ def initialize_analysis(self, parameters):
198
+ """Initialize analysis with parameters from ViewTargets"""
199
+ try:
200
+ # Reset view state
201
+ self.view.prog_bar.setValue(0)
202
+ self.view.push_button_submit.setEnabled(True)
203
+
204
+ # Reset model state
205
+ if hasattr(self.model, '_current_parameters'):
206
+ delattr(self.model, '_current_parameters')
207
+ if hasattr(self, '_off_target_results'):
208
+ delattr(self, '_off_target_results')
209
+
210
+ # Set organism in view
211
+ organism_index = self.view.combo_box_organism.findText(parameters['organism'])
212
+ if organism_index >= 0:
213
+ self.view.combo_box_organism.setCurrentIndex(organism_index)
214
+
215
+ # Set endonuclease in view
216
+ if 'endonuclease' in parameters:
217
+ endo_index = self.view.combo_box_endonuclease.findText(parameters['endonuclease'])
218
+ if endo_index >= 0:
219
+ self.view.combo_box_endonuclease.setCurrentIndex(endo_index)
220
+
221
+ # Store targets for analysis
222
+ if 'guides' in parameters:
223
+ self._targets = parameters['guides']
224
+ else:
225
+ raise ValueError("No guides provided for analysis")
226
+
227
+ # Reset radio buttons and fields
228
+ self.view.radio_button_average_output_no.setChecked(True)
229
+ self.view.radio_button_save_output_no.setChecked(True)
230
+ self.view.line_edit_output_file.clear()
231
+ self.view.line_edit_output_file.setEnabled(False)
232
+
233
+ # Reset combo box for mismatches
234
+ self.view.set_combo_box_max_mismatches()
235
+
236
+ # Enable submit button
237
+ self.view.push_button_submit.setEnabled(True)
238
+
239
+ except Exception as e:
240
+ self.logger.error(f"Error initializing analysis: {str(e)}")
241
+ self.view.show_error("Initialization Error", str(e))
242
+
243
+ @pyqtSlot(str)
244
+ def on_theme_changed(self, theme):
245
+ """Handle theme change"""
246
+ self.view.apply_theme()
247
+
248
+ def _on_progress_updated(self, value, status):
249
+ """Handle progress updates"""
250
+ try:
251
+ self.view.update_progress_bar(value, status)
252
+ except Exception as e:
253
+ self.logger.error(f"Error updating progress: {str(e)}")
src/controllers/PopulationAnalysisWindowController.py CHANGED
@@ -27,6 +27,9 @@ class PopulationAnalysisWindowController:
27
  # Tables sorting
28
  self.view.table_seed.horizontalHeader().sectionClicked.connect(self.seed_table_sorting)
29
  self.view.table_locations.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
 
 
 
30
  except Exception as e:
31
  show_error(self.global_settings, "Error setting up connections in population analysis.", str(e))
32
 
@@ -100,26 +103,27 @@ class PopulationAnalysisWindowController:
100
 
101
  def fill_data(self):
102
  try:
 
103
  self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
104
 
105
- if len(self.model.seeds) == 0:
106
- return
107
-
108
- seed_data = []
109
- for seed in self.model.seeds:
110
- data = self.model.get_seed_data(seed, self.model.db_files)
111
- processed_data = self.process_seed_data(seed, data)
112
- if processed_data: # Only add if data was processed successfully
113
- seed_data.append(processed_data)
114
-
115
- if seed_data: # Only update table if we have data
116
- self.view.update_shared_seeds_table(seed_data)
117
-
118
- if len(self.model.db_files) > 1:
119
- heatmap_data = self.model.get_heatmap_data(self.model.db_files)
120
- self.view.plot_heatmap(heatmap_data, self.model.org_names)
121
  else:
122
- self.logger.warning("No seed data was processed successfully")
123
 
124
  except Exception as e:
125
  show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
@@ -302,3 +306,43 @@ class PopulationAnalysisWindowController:
302
  event.accept()
303
  except Exception as e:
304
  show_error(self.global_settings, "Error in closeEvent() in population analysis.", str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  # Tables sorting
28
  self.view.table_seed.horizontalHeader().sectionClicked.connect(self.seed_table_sorting)
29
  self.view.table_locations.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
30
+
31
+ # Add new connection for export button
32
+ self.view.push_button_export_selected_gRNAs.clicked.connect(self.export_selected_seeds)
33
  except Exception as e:
34
  show_error(self.global_settings, "Error setting up connections in population analysis.", str(e))
35
 
 
103
 
104
  def fill_data(self):
105
  try:
106
+ # Get seeds shared between ALL organisms
107
  self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
108
 
109
+ if len(self.model.seeds) > 0:
110
+ # Process seed data for the table
111
+ seed_data = []
112
+ for seed in self.model.seeds:
113
+ data = self.model.get_seed_data(seed, self.model.db_files)
114
+ processed_data = self.process_seed_data(seed, data)
115
+ if processed_data: # Only add if data was processed successfully
116
+ seed_data.append(processed_data)
117
+
118
+ if seed_data: # Only update table if we have data
119
+ self.view.update_shared_seeds_table(seed_data)
120
+
121
+ # Always generate and display heatmap for 2 or more organisms
122
+ if len(self.model.db_files) > 1:
123
+ heatmap_data = self.model.get_heatmap_data(self.model.db_files)
124
+ self.view.plot_heatmap(heatmap_data, self.model.org_names)
125
  else:
126
+ self.logger.warning("Not enough organisms selected for heatmap")
127
 
128
  except Exception as e:
129
  show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
 
306
  event.accept()
307
  except Exception as e:
308
  show_error(self.global_settings, "Error in closeEvent() in population analysis.", str(e))
309
+
310
+ def export_selected_seeds(self):
311
+ try:
312
+ selected_seeds = []
313
+ selected_rows = self.view.table_seed.selectionModel().selectedRows()
314
+ self.logger.debug(f"Selected rows: {selected_rows}")
315
+
316
+ if not selected_rows:
317
+ show_message(
318
+ fontSize=12,
319
+ icon=QtWidgets.QMessageBox.Icon.Critical,
320
+ title="Nothing Selected",
321
+ message="No seeds were selected. Please select seeds to export."
322
+ )
323
+ return
324
+
325
+ for row_idx in selected_rows:
326
+ seed_data = {
327
+ # Seed, % Coverage, Total Repeats, Avg. Repeats/Scaffold, Consensus Sequence, Full Sequence, % Consensus, Score, PAM, Strand
328
+ # ('TCCCTGGTTCGAATCC', 100.0, 4, 2.0, 'TTGGTCCCTGGTTCGAATCC', 50.0, '55', 'GGG', '-')
329
+ 'seed': self.view.table_seed.item(row_idx.row(), 0).text(), # Seed column
330
+ 'percent_coverage': self.view.table_seed.item(row_idx.row(), 1).text(), # % Coverage column
331
+ 'total_repeats': self.view.table_seed.item(row_idx.row(), 2).text(), # Total Repeats column
332
+ 'avg_repeats_or_scaffold': self.view.table_seed.item(row_idx.row(), 3).text(), # Avg. Repeats/Scaffold column
333
+ 'consensus_sequence': self.view.table_seed.item(row_idx.row(), 4).text(), # Consensus Sequence column
334
+ 'full_sequence': self.view.table_seed.item(row_idx.row(), 4).text(), # Full Sequence column
335
+ 'percent_consensus': self.view.table_seed.item(row_idx.row(), 5).text(), # % Consensus column
336
+ 'score': self.view.table_seed.item(row_idx.row(), 6).text(), # Score column
337
+ 'pam': self.view.table_seed.item(row_idx.row(), 7).text(), # PAM column
338
+ 'strand': self.view.table_seed.item(row_idx.row(), 8).text(), # Strand column
339
+ }
340
+ selected_seeds.append(seed_data)
341
+
342
+ # Get export window from global settings and show dialog
343
+ export_window = self.global_settings.get_export_selected_grnas_window()
344
+ print(f"Selected seeds: {selected_seeds}")
345
+ export_window.show_dialog(selected_seeds, "Population Analysis")
346
+
347
+ except Exception as e:
348
+ show_error(self.global_settings, "Error exporting selected seeds", str(e))
src/controllers/StartupWindowController.py CHANGED
@@ -3,6 +3,7 @@ from PyQt6 import QtWidgets
3
  from models.StartupWindowModel import StartupWindowModel
4
  from utils.ui import show_message, show_error
5
  from views.StartupWindowView import StartupWindowView
 
6
 
7
  class StartupWindowController:
8
  def __init__(self, global_settings):
@@ -66,12 +67,24 @@ class StartupWindowController:
66
  self.logger.debug(f"Handle go to home or new genome: {self.model.get_db_path()}")
67
  is_valid, message = self.settings.validate_db_path(self.model.get_db_path())
68
  if is_valid:
69
- self.settings.set_first_time_startup_completed() # New method call
70
- self.settings.main_window._switch_to_home_from_startup()
71
  else:
72
  self.logger.warning(f"Invalid database path: {message}")
73
  self.open_new_genome_tab()
74
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  def open_new_genome_tab(self):
76
  try:
77
  self.logger.debug("Opening New Genome tab")
 
3
  from models.StartupWindowModel import StartupWindowModel
4
  from utils.ui import show_message, show_error
5
  from views.StartupWindowView import StartupWindowView
6
+ import sys
7
 
8
  class StartupWindowController:
9
  def __init__(self, global_settings):
 
67
  self.logger.debug(f"Handle go to home or new genome: {self.model.get_db_path()}")
68
  is_valid, message = self.settings.validate_db_path(self.model.get_db_path())
69
  if is_valid:
70
+ self.settings.set_first_time_startup_completed()
71
+ self.restart_application()
72
  else:
73
  self.logger.warning(f"Invalid database path: {message}")
74
  self.open_new_genome_tab()
75
 
76
+ def restart_application(self):
77
+ """Restart the entire application"""
78
+ try:
79
+ self.logger.info("Restarting application...")
80
+ # Get the current application instance
81
+ app = QtWidgets.QApplication.instance()
82
+ # Use a custom exit code for restart (e.g., 1000)
83
+ app.exit(1000) # Changed from QApplication.Exit.ExitCode.Restart
84
+ except Exception as e:
85
+ self.logger.error(f"Error restarting application: {str(e)}", exc_info=True)
86
+ show_error(self.settings, "Error restarting application", str(e))
87
+
88
  def open_new_genome_tab(self):
89
  try:
90
  self.logger.debug("Opening New Genome tab")
src/controllers/ViewTargetsController.py CHANGED
@@ -9,317 +9,565 @@ from PyQt6 import QtWidgets, QtCore
9
  import traceback
10
  import threading
11
  from Bio.Seq import Seq
 
12
 
13
  class ViewTargetsController:
14
  def __init__(self, global_settings):
15
- self.global_settings = global_settings
16
- start_time = time.time()
17
  self.model = ViewTargetsModel(global_settings)
18
  self.view = ViewTargetsView(global_settings)
19
- init_time = time.time() - start_time
20
- self.global_settings.logger.debug(f"ViewTargets initialization took: {init_time:.2f} seconds")
21
 
22
  self.setup_connections()
23
  self.organism = ""
24
  self.endonuclease = ""
 
25
 
26
  def setup_connections(self):
27
  self.view.push_button_off_target.clicked.connect(self.perform_off_target_analysis)
28
  self.view.push_button_cotargeting.clicked.connect(self.perform_cotargeting)
29
  self.view.push_button_highlight_guides.clicked.connect(self.highlight_gene_viewer)
30
- self.view.push_button_export_grna.clicked.connect(self.export_targets)
31
- self.view.push_button_filter_options.clicked.connect(self.show_filter_options)
32
  self.view.push_button_scoring_options.clicked.connect(self.show_scoring_options)
33
  self.view.push_button_change_location.clicked.connect(self.change_indices)
34
  self.view.push_button_reset_location.clicked.connect(self.reset_location)
35
  self.view.check_box_select_all.stateChanged.connect(self.select_all)
36
- self.view.combo_box_gene.currentIndexChanged.connect(self.display_gene_data)
37
  self.view.gene_selected.connect(self.on_gene_selected)
 
 
 
38
 
39
- def load_targets(self, selected_targets, organism, endonuclease):
40
  try:
41
- total_start = time.time()
42
-
43
  self.organism = organism
44
  self.endonuclease = endonuclease
 
 
 
45
 
46
- # Time model loading
47
- model_start = time.time()
48
- self.model.load_targets(selected_targets, organism, endonuclease)
49
- model_time = time.time() - model_start
50
- self.global_settings.logger.debug(f"Model load_targets took: {model_time:.2f} seconds")
51
-
52
- # Time getting targets
53
- targets_start = time.time()
54
- targets = self.model.get_targets()
55
- targets_time = time.time() - targets_start
56
- self.global_settings.logger.debug(f"Getting targets took: {targets_time:.2f} seconds")
57
 
58
- # Get feature ID mapping from FindTargetsModel - Optimized with timing
59
- genes_start = time.time()
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- # Time set creation
62
- set_start = time.time()
63
- seen_genes = set()
64
  formatted_genes = []
65
- set_time = time.time() - set_start
66
- self.global_settings.logger.debug(f"Set initialization took: {set_time:.2f} seconds")
67
-
68
- # Time target processing
69
- process_start = time.time()
70
- for target in selected_targets:
71
- gene_name = target.get('feature_name')
72
- feature_id = target.get('feature_id')
73
 
74
- if gene_name and feature_id and gene_name not in seen_genes:
75
- seen_genes.add(gene_name)
76
- formatted_genes.append(f"{feature_id}: {gene_name}")
77
- process_time = time.time() - process_start
78
- self.global_settings.logger.debug(f"Target processing took: {process_time:.2f} seconds")
79
-
80
- # Time sorting
81
- sort_start = time.time()
82
- formatted_genes.sort()
83
- sort_time = time.time() - sort_start
84
- self.global_settings.logger.debug(f"Sorting took: {sort_time:.2f} seconds")
 
 
 
 
85
 
86
- # Time view update
87
- view_start = time.time()
88
  self.view.set_combo_box_gene(formatted_genes)
89
- view_time = time.time() - view_start
90
- self.global_settings.logger.debug(f"View update took: {view_time:.2f} seconds")
91
-
92
- genes_time = time.time() - genes_start
93
- self.global_settings.logger.debug(f"Total setting genes took: {genes_time:.2f} seconds")
94
-
95
- # Time displaying targets
96
- display_start = time.time()
97
- self.view.display_targets_in_table(targets)
98
- display_time = time.time() - display_start
99
- self.global_settings.logger.debug(f"Displaying targets took: {display_time:.2f} seconds")
100
-
101
- # Time setting endonuclease
102
- endo_start = time.time()
103
- self.view.set_combo_box_endonuclease([endonuclease])
104
- endo_time = time.time() - endo_start
105
- self.global_settings.logger.debug(f"Setting endonuclease took: {endo_time:.2f} seconds")
106
-
107
- # Time loading gene viewer
108
- gene_start = time.time()
109
- self.load_gene_viewer()
110
- gene_time = time.time() - gene_start
111
- self.global_settings.logger.debug(f"Loading gene viewer took: {gene_time:.2f} seconds")
112
 
113
- total_time = time.time() - total_start
114
- self.global_settings.logger.debug(f"Total load_targets took: {total_time:.2f} seconds")
115
 
 
 
 
 
 
116
  except Exception as e:
117
- show_error(self.global_settings, "Error loading targets", str(e))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  def load_gene_viewer(self):
120
- """Load gene viewer with sequence and location information"""
121
  try:
122
- total_start = time.time()
123
 
124
  # Get selected gene from combo box
125
- combo_start = time.time()
126
  selected_text = self.view.combo_box_gene.currentText()
127
  if not selected_text:
128
- self.global_settings.logger.debug("No gene selected")
129
  return
130
- combo_time = time.time() - combo_start
131
- self.global_settings.logger.debug(f"Combo box access time: {combo_time:.2f} seconds")
132
 
133
  # Extract locus tag from "locus_tag: gene_name" format
134
- parse_start = time.time()
135
  locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
136
- self.global_settings.logger.debug(f"Loading sequence for locus tag: {locus_tag}")
137
- parse_time = time.time() - parse_start
138
- self.global_settings.logger.debug(f"Locus tag parsing time: {parse_time:.2f} seconds")
139
 
140
  # Get gene sequence with padding
141
- sequence_start = time.time()
142
  sequence_data = self.model.get_gene_sequence(locus_tag)
143
- sequence_time = time.time() - sequence_start
144
- self.global_settings.logger.debug(f"Sequence retrieval time: {sequence_time:.2f} seconds")
145
 
146
  if sequence_data:
147
  # Update gene viewer with sequence
148
- viewer_start = time.time()
149
  self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
150
- viewer_time = time.time() - viewer_start
151
- self.global_settings.logger.debug(f"Text viewer update time: {viewer_time:.2f} seconds")
152
 
153
  # Update location fields
154
- location_start = time.time()
155
  self.view.line_edit_start_location.setText(str(sequence_data['start']))
156
  self.view.line_edit_stop_location.setText(str(sequence_data['end']))
157
- location_time = time.time() - location_start
158
- self.global_settings.logger.debug(f"Location fields update time: {location_time:.2f} seconds")
159
 
160
- total_time = time.time() - total_start
161
- self.global_settings.logger.debug(f"Total gene viewer loading took: {total_time:.2f} seconds")
162
  else:
163
- self.global_settings.logger.warning(f"No sequence data found for locus tag {locus_tag}")
164
 
165
  except Exception as e:
166
- self.global_settings.logger.error(f"Error in load_gene_viewer: {str(e)}")
167
- self.global_settings.logger.error(f"Stack trace: {traceback.format_exc()}")
168
 
169
  def perform_off_target_analysis(self):
 
170
  try:
171
- selected_targets = self.view.get_selected_targets()
172
- if not selected_targets:
173
- QMessageBox.warning(self.view, "No Selection", "Please select targets for off-target analysis.")
 
 
 
 
 
174
  return
175
- # Implement off-target analysis logic here
176
- # You might want to create a new controller for off-target analysis
177
- off_target_controller = self.global_settings.get_off_target_window()
178
- off_target_controller.analyze(selected_targets, self.organism, self.endonuclease)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  except Exception as e:
180
- show_error(self.global_settings, "Error in off-target analysis", str(e))
 
 
181
 
182
  def perform_cotargeting(self):
 
183
  try:
184
- selected_targets = self.view.get_selected_targets()
185
- if not selected_targets:
186
- QMessageBox.warning(self.view, "No Selection", "Please select targets for cotargeting.")
 
 
 
 
 
 
 
187
  return
188
- # Implement cotargeting logic here
189
- # You might want to create a new controller for cotargeting
190
- cotargeting_controller = self.global_settings.get_cotargeting_window()
191
- cotargeting_controller.analyze(selected_targets, self.organism, self.endonuclease)
 
 
 
 
192
  except Exception as e:
193
- show_error(self.global_settings, "Error in cotargeting", str(e))
 
 
194
 
195
  def highlight_gene_viewer(self):
196
- """Highlight selected targets in gene viewer"""
197
  try:
198
- self.global_settings.logger.debug("Starting highlight_gene_viewer")
199
 
200
- # Get selected targets
201
- selected_rows = self.view.get_selected_targets()
202
- self.global_settings.logger.debug(f"Selected targets: {selected_rows}")
203
 
204
  if not selected_rows:
205
  QMessageBox.warning(self.view, "No Selection",
206
- "Please select targets to highlight in the gene viewer.")
207
  return
208
 
209
  # Convert table selections to the format expected by the model
210
- targets_to_highlight = []
211
- for target in selected_rows:
212
- target_info = {
213
- 'location': target['location'],
214
- 'sequence': target['sequence'],
215
- 'strand': target['strand']
216
  }
217
- targets_to_highlight.append(target_info)
218
- self.global_settings.logger.debug(f"Target to highlight: {target_info}")
219
 
220
  # Get current gene sequence
221
  current_gene = self.view.combo_box_gene.currentText()
222
- locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
223
- self.global_settings.logger.debug(f"Getting sequence for locus tag: {locus_tag}")
224
 
225
- # Get gene sequence with padding
226
- sequence_data = self.model.get_gene_sequence(locus_tag)
227
- if not sequence_data or 'sequence' not in sequence_data:
228
- self.global_settings.logger.error("No sequence data found")
229
- QMessageBox.warning(self.view, "No Gene Data",
230
- "Could not get gene sequence for highlighting.")
231
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
- self.global_settings.logger.debug(f"Gene sequence length: {len(sequence_data['sequence'])}")
234
 
235
  # Highlight the sequences
236
- if targets_to_highlight:
237
- self.global_settings.logger.debug("Attempting to highlight sequences")
238
- self.highlight_targets_in_gene_viewer(targets_to_highlight)
239
  else:
240
- self.global_settings.logger.error("No valid targets to highlight")
241
- QMessageBox.warning(self.view, "No Valid Targets",
242
  "Could not get sequence information from the selected rows.")
243
 
244
  except Exception as e:
245
- self.global_settings.logger.error(f"Error in highlight_gene_viewer: {str(e)}\n{traceback.format_exc()}")
246
- show_error(self.global_settings, "Error highlighting gene viewer", str(e))
247
 
248
  def export_targets(self):
249
  try:
250
- selected_targets = self.view.get_selected_targets()
251
- if not selected_targets:
252
- QMessageBox.warning(self.view, "No Selection", "Please select targets to export.")
 
 
 
 
 
253
  return
254
- file_path = self.view.get_export_file_path()
255
- if file_path:
256
- self.model.export_targets(selected_targets, file_path)
257
- QMessageBox.information(self.view, "Export Successful", "Selected targets have been exported successfully.")
258
- except Exception as e:
259
- show_error(self.global_settings, "Error exporting targets", str(e))
260
 
261
- def show_filter_options(self):
262
- try:
263
- filter_options = self.model.get_filter_options()
264
- self.view.show_filter_options_dialog(filter_options)
265
- if self.view.filter_options_accepted():
266
- new_options = self.view.get_filter_options()
267
- self.model.set_filter_options(new_options)
268
- self.refresh_targets_display()
269
  except Exception as e:
270
- show_error(self.global_settings, "Error showing filter options", str(e))
 
 
271
 
272
  def show_scoring_options(self):
273
- """Show scoring options window"""
274
  try:
275
  # Create scoring options controller if not exists
276
  if not hasattr(self, '_scoring_options_controller'):
277
  # Create controller with self as view_targets_controller
278
  self._scoring_options_controller = ScoringOptionsController(
279
- global_settings=self.global_settings,
280
  view_targets_controller=self
281
  )
282
 
283
- # Show scoring options window
284
  self._scoring_options_controller.show()
285
 
286
  except Exception as e:
287
- self.global_settings.logger.error(f"Error showing scoring options: {str(e)}")
288
- self.global_settings.logger.error(f"Stack trace: {traceback.format_exc()}")
289
- show_error(self.global_settings, "Error", f"Could not show scoring options: {str(e)}")
290
 
291
  def change_indices(self):
 
292
  try:
293
- start = int(self.view.line_edit_start_location.text())
294
- end = int(self.view.line_edit_stop_location.text())
295
- if self.model.update_gene_viewer_indices(start, end):
296
- self.view.set_text_edit_gene_viewer(self.model.gene_sequence)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  else:
298
- QMessageBox.warning(self.view, "Invalid Range", "Please enter valid start and end positions within the gene range.")
299
- except ValueError:
300
- QMessageBox.warning(self.view, "Invalid Input", "Please enter valid integer values for start and end positions.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  except Exception as e:
302
- show_error(self.global_settings, "Error changing indices", str(e))
 
 
303
 
304
  def reset_location(self):
 
305
  try:
306
- self.model.reset_gene_viewer_indices()
307
- gene_data = self.model.get_gene_data(self.view.combo_box_gene.currentText())
308
- self.view.set_text_edit_gene_viewer(gene_data['sequence'])
309
- self.view.line_edit_start_location.setText(str(gene_data['start']))
310
- self.view.line_edit_stop_location.setText(str(gene_data['end']))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  except Exception as e:
312
- show_error(self.global_settings, "Error resetting location", str(e))
 
 
313
 
314
  def select_all(self, state):
315
  try:
316
- self.view.select_all_targets(state == 2) # 2 corresponds to Qt.Checked
317
  except Exception as e:
318
- show_error(self.global_settings, "Error selecting all targets", str(e))
319
 
320
  def display_gene_data(self, gene_name):
321
  try:
322
  gene_data = self.model.get_gene_data(gene_name)
 
323
  if gene_data and gene_data['sequence']:
324
  self.view.set_text_edit_gene_viewer(gene_data['sequence'])
325
  self.view.line_edit_start_location.setText(str(gene_data['start']))
@@ -329,14 +577,18 @@ class ViewTargetsController:
329
  self.view.line_edit_start_location.clear()
330
  self.view.line_edit_stop_location.clear()
331
  except Exception as e:
332
- show_error(self.global_settings, "Error displaying gene data", str(e))
333
 
334
- def refresh_targets_display(self):
 
335
  try:
336
- filtered_targets = self.model.get_filtered_targets()
337
- self.view.display_targets_in_table(filtered_targets)
 
 
 
338
  except Exception as e:
339
- show_error(self.global_settings, "Error refreshing targets display", str(e))
340
 
341
  def show(self):
342
  self.view.show()
@@ -344,102 +596,156 @@ class ViewTargetsController:
344
  def on_gene_selected(self, selected_text):
345
  """Handle gene selection signal"""
346
  try:
347
- # Extract locus tag from "locus_tag: gene_name" format
348
- locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
349
- self.global_settings.logger.debug(f"Loading sequence for locus tag: {locus_tag}")
350
-
351
- # Get gene sequence with padding using locus tag
352
- sequence_data = self.model.get_gene_sequence(locus_tag)
353
- if sequence_data:
354
- # Update gene viewer with sequence
355
- self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
356
-
357
- # Update location fields
358
- self.view.line_edit_start_location.setText(str(sequence_data['start']))
359
- self.view.line_edit_stop_location.setText(str(sequence_data['end']))
360
-
361
- self.global_settings.logger.debug(f"Updated gene viewer with sequence of length: {len(sequence_data['sequence'])}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  else:
363
- self.global_settings.logger.warning(f"No sequence data found for locus tag {locus_tag}")
364
- self.view.set_text_edit_gene_viewer("No sequence data available for this gene")
365
- self.view.line_edit_start_location.clear()
366
- self.view.line_edit_stop_location.clear()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
  except Exception as e:
369
- self.global_settings.logger.error(f"Error handling gene selection: {str(e)}")
370
- self.global_settings.logger.error(f"Stack trace: {traceback.format_exc()}")
371
 
372
- def highlight_targets_in_gene_viewer(self, targets_to_highlight=None):
373
- """Highlight selected targets in gene viewer"""
374
  try:
375
- self.global_settings.logger.debug("Starting highlight_gene_viewer")
376
 
377
- # Get selected targets if none provided
378
- if targets_to_highlight is None:
379
- targets_to_highlight = self.view.get_selected_targets()
380
 
381
- self.global_settings.logger.debug(f"Selected targets: {targets_to_highlight}")
382
 
383
- if not targets_to_highlight:
384
  QMessageBox.warning(self.view, "No Selection",
385
- "Please select targets to highlight in the gene viewer.")
386
  return
387
 
388
  # Get current gene sequence
389
  selected_text = self.view.combo_box_gene.currentText()
390
- locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
391
 
392
- sequence_data = self.model.get_gene_sequence(locus_tag)
393
- if not sequence_data or 'sequence' not in sequence_data:
394
- self.global_settings.logger.error("No sequence data available for highlighting")
395
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
- sequence = sequence_data['sequence']
398
-
399
- # Sort targets by position for efficient highlighting
400
  highlights = []
401
  sequences_found = 0
402
- total_sequences = len(targets_to_highlight)
403
 
404
- for target in targets_to_highlight:
405
- self.global_settings.logger.debug(f"Processing target: {target}")
406
- sequence_to_find = target['sequence']
407
- strand = target['strand']
408
 
409
- # For negative strand, we need to use reverse complement
410
  if strand == '-':
411
  sequence_to_find = str(Seq(sequence_to_find).reverse_complement())
412
- self.global_settings.logger.debug(f"Reverse complemented sequence: {sequence_to_find}")
413
 
414
- # Search for the sequence in the gene viewer text
415
  sequence_upper = sequence.upper()
416
  target_upper = sequence_to_find.upper()
417
 
418
- self.global_settings.logger.debug(f"Searching for sequence: {target_upper}")
419
 
420
- # Find all occurrences
421
  pos = sequence_upper.find(target_upper)
422
  if pos != -1:
423
- self.global_settings.logger.debug(f"Found sequence at position: {pos}")
424
  color = 'red' if strand == '-' else 'green'
425
  highlights.append((pos, len(sequence_to_find), color))
426
  sequences_found += 1
427
  else:
428
- self.global_settings.logger.debug(f"Sequence not found: {target_upper}")
429
 
430
- # Only show warning if NO sequences were found
431
  if sequences_found == 0:
432
- self.global_settings.logger.warning("No sequences could be highlighted")
433
  QMessageBox.warning(self.view, "Highlighting Failed",
434
  "Could not highlight any of the selected sequences in the current gene view.")
435
  return
436
 
437
- self.global_settings.logger.debug(f"Found {sequences_found} out of {total_sequences} sequences to highlight")
438
-
439
  # Build highlighted sequence
440
  result = []
441
  last_pos = 0
442
- for pos, length, color in sorted(highlights): # Sort highlights by position
443
  result.append(sequence[last_pos:pos])
444
  result.append(f"<span style='background-color: {color};'>")
445
  result.append(sequence[pos:pos+length])
@@ -451,11 +757,11 @@ class ViewTargetsController:
451
 
452
  # Update the view with highlighted sequence
453
  self.view.update_gene_viewer(highlighted_sequence)
454
- self.global_settings.logger.debug(f"Successfully highlighted {sequences_found} sequences")
455
 
456
  except Exception as e:
457
- self.global_settings.logger.error(f"Error highlighting targets: {str(e)}")
458
- self.global_settings.logger.error(f"Stack trace: {traceback.format_exc()}")
459
 
460
  def update_scores(self, scores, algorithm):
461
  """Update the table with new scores from alternative scoring methods"""
@@ -464,9 +770,9 @@ class ViewTargetsController:
464
  headers = self.view.get_table_headers()
465
 
466
  # Get selected rows
467
- selected_rows = sorted(set(index.row() for index in self.view.table_targets.selectedIndexes()))
468
  if not selected_rows:
469
- self.global_settings.logger.warning("No rows selected for scoring")
470
  return
471
 
472
  # Determine the position for the new column (after the "Score" column)
@@ -474,34 +780,32 @@ class ViewTargetsController:
474
  desired_index = score_index + 1
475
 
476
  # Disable updates to prevent crashes
477
- self.view.table_targets.setUpdatesEnabled(False)
478
 
479
  try:
480
  # Add new column for algorithm if it doesn't exist
481
  if algorithm not in headers:
482
- # Store current column count
483
- current_cols = self.view.table_targets.columnCount()
484
 
485
  # Insert new column after Score
486
- self.view.table_targets.insertColumn(desired_index)
487
 
488
  # Set header for new column
489
- self.view.table_targets.setHorizontalHeaderItem(
490
  desired_index,
491
  QtWidgets.QTableWidgetItem(algorithm)
492
  )
493
 
494
  # Move Off-Target and Details columns one position right
495
- for row in range(self.view.table_targets.rowCount()):
496
  # Move Off-Target
497
- off_target_item = self.view.table_targets.takeItem(row, desired_index)
498
  if off_target_item:
499
- self.view.table_targets.setItem(row, desired_index + 1, off_target_item)
500
 
501
  # Move Details button
502
- details_widget = self.view.table_targets.cellWidget(row, desired_index)
503
  if details_widget:
504
- self.view.table_targets.setCellWidget(row, desired_index + 1, details_widget)
505
 
506
  col_index = desired_index
507
  else:
@@ -514,22 +818,180 @@ class ViewTargetsController:
514
  # Round to 2 decimal places
515
  rounded_score = round(float(scores[score_idx]), 2)
516
  score_item.setData(QtCore.Qt.ItemDataRole.EditRole, rounded_score)
517
- self.view.table_targets.setItem(row, col_index, score_item)
518
 
519
- # Also update the target data to preserve score during filtering/sorting
520
  if hasattr(self.view, '_all_results'):
521
  self.view._all_results[row]['azimuth_score'] = rounded_score
522
 
523
  # Resize columns to fit new content
524
- self.view.table_targets.resizeColumnsToContents()
525
 
526
- self.global_settings.logger.debug(f"Updated scores for algorithm: {algorithm}")
527
- self.global_settings.logger.debug(f"Updated rows: {selected_rows}")
528
 
529
  finally:
530
  # Re-enable updates
531
- self.view.table_targets.setUpdatesEnabled(True)
532
 
533
  except Exception as e:
534
- self.global_settings.logger.error(f"Error updating scores: {str(e)}")
535
  raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  import traceback
10
  import threading
11
  from Bio.Seq import Seq
12
+ import os
13
 
14
  class ViewTargetsController:
15
  def __init__(self, global_settings):
16
+ self.settings = global_settings
 
17
  self.model = ViewTargetsModel(global_settings)
18
  self.view = ViewTargetsView(global_settings)
19
+ self.logger = global_settings.get_logger()
 
20
 
21
  self.setup_connections()
22
  self.organism = ""
23
  self.endonuclease = ""
24
+ self.selected_targets = None
25
 
26
  def setup_connections(self):
27
  self.view.push_button_off_target.clicked.connect(self.perform_off_target_analysis)
28
  self.view.push_button_cotargeting.clicked.connect(self.perform_cotargeting)
29
  self.view.push_button_highlight_guides.clicked.connect(self.highlight_gene_viewer)
30
+ self.view.push_button_clear_guides.clicked.connect(self.clear_highlighted_guides)
31
+ self.view.push_button_export_selected_grnas.clicked.connect(self.export_targets)
32
  self.view.push_button_scoring_options.clicked.connect(self.show_scoring_options)
33
  self.view.push_button_change_location.clicked.connect(self.change_indices)
34
  self.view.push_button_reset_location.clicked.connect(self.reset_location)
35
  self.view.check_box_select_all.stateChanged.connect(self.select_all)
36
+ # self.view.combo_box_gene.currentIndexChanged.connect(self.display_gene_data)
37
  self.view.gene_selected.connect(self.on_gene_selected)
38
+
39
+ self.view.check_box_filter_5_prime_g_sequences.stateChanged.connect(self.refresh_guides_display)
40
+ self.view.spin_box_minimum_on_target_score.valueChanged.connect(self.refresh_guides_display)
41
 
42
+ def load_guides(self, selected_targets, organism, endonuclease):
43
  try:
 
 
44
  self.organism = organism
45
  self.endonuclease = endonuclease
46
+ self.selected_targets = selected_targets
47
+
48
+ print(f"Loading guides for {organism} and {selected_targets} with {endonuclease}")
49
 
50
+ self.model.load_guides(selected_targets, organism, endonuclease)
 
 
 
 
 
 
 
 
 
 
51
 
52
+ # Get available endonucleases for this organism
53
+ org_to_endo = self.settings.get_organism_to_endonuclease()
54
+ if organism in org_to_endo:
55
+ available_endos = org_to_endo[organism]
56
+ self.view.combo_box_endonuclease.clear()
57
+ self.view.combo_box_endonuclease.addItems(available_endos)
58
+
59
+ # Set current endonuclease
60
+ current_index = self.view.combo_box_endonuclease.findText(endonuclease)
61
+ if current_index >= 0:
62
+ self.view.combo_box_endonuclease.setCurrentIndex(current_index)
63
+
64
+ self.view.combo_box_endonuclease.currentTextChanged.connect(self._on_endonuclease_changed)
65
 
66
+ # Format gene names for display
67
+ seen_positions = set()
 
68
  formatted_genes = []
69
+
70
+ if selected_targets and selected_targets[0].get('feature_type') == 'Position':
71
+ position_groups = {}
72
+ for target in selected_targets:
73
+ position_name = target['feature_id']
74
+ if position_name not in position_groups:
75
+ position_groups[position_name] = target
76
+ formatted_genes.append(position_name)
77
 
78
+ if formatted_genes:
79
+ first_guide = position_groups[formatted_genes[0]]
80
+ self.view.line_edit_start_location.setText(str(first_guide['start']))
81
+ self.view.line_edit_stop_location.setText(str(first_guide['end']))
82
+
83
+ if 'gene_sequence' in first_guide:
84
+ self.view.set_text_edit_gene_viewer(first_guide['gene_sequence'])
85
+ else:
86
+ for target in selected_targets:
87
+ gene_name = target.get('feature_name')
88
+ feature_id = target.get('feature_id')
89
+
90
+ if gene_name and feature_id and gene_name not in seen_positions:
91
+ seen_positions.add(gene_name)
92
+ formatted_genes.append(f"{feature_id}: {gene_name}")
93
 
94
+ formatted_genes.sort()
 
95
  self.view.set_combo_box_gene(formatted_genes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ guides = self.model.get_guides()
98
+ self.view.display_guides_in_table(guides)
99
 
100
+ # Trigger gene sequence retrieval for first entry
101
+ if formatted_genes:
102
+ first_gene = formatted_genes[0]
103
+ self.on_gene_selected(first_gene)
104
+
105
  except Exception as e:
106
+ self.logger.error(f"Error in load_guides: {str(e)}")
107
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
108
+ show_error(self.settings, "Error loading guides", str(e))
109
+
110
+ def _on_endonuclease_changed(self, new_endonuclease):
111
+ try:
112
+ if new_endonuclease != self.endonuclease:
113
+ self.logger.debug(f"Changing endonuclease from {self.endonuclease} to {new_endonuclease}")
114
+ self.endonuclease = new_endonuclease
115
+
116
+ # Check if this is a co-targeting endonuclease combination
117
+ if '|' in new_endonuclease:
118
+ selected_endos = new_endonuclease.split('|')
119
+ # Rerun co-targeting logic with selected endonucleases
120
+ self.handle_cotargeting_result(selected_endos)
121
+ else:
122
+ # Regular single endonuclease handling
123
+ updated_targets = []
124
+ for target in self.selected_targets:
125
+ new_target = target.copy()
126
+ new_target['endonuclease'] = new_endonuclease
127
+ updated_targets.append(new_target)
128
+
129
+ self.logger.debug(f"Created {len(updated_targets)} updated targets for {new_endonuclease}")
130
+
131
+ # Update model with new targets
132
+ self.model.load_guides(updated_targets, self.organism, new_endonuclease)
133
+ guides = self.model.get_guides()
134
+
135
+ self.logger.debug(f"Got {len(guides)} guides from model")
136
+
137
+ # Update display
138
+ self.view.display_guides_in_table(guides)
139
+
140
+ except Exception as e:
141
+ self.logger.error(f"Error changing endonuclease: {str(e)}")
142
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
143
+ show_error(self.settings, "Error", f"Could not change endonuclease: {str(e)}")
144
 
145
  def load_gene_viewer(self):
 
146
  try:
 
147
 
148
  # Get selected gene from combo box
 
149
  selected_text = self.view.combo_box_gene.currentText()
150
  if not selected_text:
151
+ self.logger.debug("No gene selected")
152
  return
 
 
153
 
154
  # Extract locus tag from "locus_tag: gene_name" format
 
155
  locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
156
+ self.logger.debug(f"Loading sequence for locus tag: {locus_tag}")
 
 
157
 
158
  # Get gene sequence with padding
 
159
  sequence_data = self.model.get_gene_sequence(locus_tag)
 
 
160
 
161
  if sequence_data:
162
  # Update gene viewer with sequence
 
163
  self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
 
 
164
 
165
  # Update location fields
 
166
  self.view.line_edit_start_location.setText(str(sequence_data['start']))
167
  self.view.line_edit_stop_location.setText(str(sequence_data['end']))
 
 
168
 
 
 
169
  else:
170
+ self.logger.warning(f"No sequence data found for locus tag {locus_tag}")
171
 
172
  except Exception as e:
173
+ self.logger.error(f"Error in load_gene_viewer: {str(e)}")
174
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
175
 
176
  def perform_off_target_analysis(self):
177
+ """Launch off-target analysis for selected guides"""
178
  try:
179
+ # Get selected guides
180
+ selected_guides = self.view.get_selected_guides()
181
+ if not selected_guides:
182
+ QtWidgets.QMessageBox.warning(
183
+ self.view,
184
+ "No Selection",
185
+ "Please select guides for off-target analysis."
186
+ )
187
  return
188
+
189
+ # Create off-target controller if not exists
190
+ if not hasattr(self, '_off_target_controller'):
191
+ from controllers.OffTargetController import OffTargetController
192
+ self._off_target_controller = OffTargetController(self.settings)
193
+
194
+ # Connect to results signal
195
+ self._off_target_controller.off_target_results_ready.connect(
196
+ self._handle_off_target_results
197
+ )
198
+
199
+ # Set initial parameters based on current organism/endonuclease
200
+ parameters = {
201
+ 'organism': self.organism,
202
+ 'endonuclease': self.endonuclease,
203
+ 'guides': selected_guides # Pass the selected guides
204
+ }
205
+
206
+ # Initialize analysis with parameters
207
+ self._off_target_controller.initialize_analysis(parameters)
208
+
209
+ # Show and bring window to front
210
+ self._off_target_controller.show()
211
+
212
+ except Exception as e:
213
+ self.logger.error(f"Error launching off-target analysis: {str(e)}")
214
+ show_error(self.settings, "Error", f"Could not launch off-target analysis: {str(e)}")
215
+
216
+ def _handle_off_target_results(self, results):
217
+ """Handle off-target analysis results"""
218
+ try:
219
+ scores, details = results # Unpack the tuple of results
220
+
221
+ # Get current table headers
222
+ headers = self.view.get_table_headers()
223
+
224
+ # Find Score column index
225
+ score_index = headers.index("Score")
226
+
227
+ # Add Off-Target column if it doesn't exist
228
+ if "Off-Target" not in headers:
229
+ self.view.table_guides.insertColumn(score_index + 1)
230
+ self.view.table_guides.setHorizontalHeaderItem(
231
+ score_index + 1,
232
+ QtWidgets.QTableWidgetItem("Off-Target")
233
+ )
234
+
235
+ off_target_index = headers.index("Off-Target") if "Off-Target" in headers else score_index + 1
236
+
237
+ # Update the view with results and details
238
+ self.view.update_off_target_details(scores, details)
239
+
240
+ self.logger.debug(f"Updated off-target scores in table with {len(details) if details else 0} detailed results")
241
+
242
  except Exception as e:
243
+ self.logger.error(f"Error handling off-target results: {str(e)}")
244
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
245
+ show_error(self.settings, "Error", f"Could not update off-target scores: {str(e)}")
246
 
247
  def perform_cotargeting(self):
248
+ """Launch co-targeting analysis"""
249
  try:
250
+ # Get current endonuclease choices
251
+ current_items = [self.view.combo_box_endonuclease.itemText(i)
252
+ for i in range(self.view.combo_box_endonuclease.count())]
253
+
254
+ if len(current_items) <= 1:
255
+ QtWidgets.QMessageBox.warning(
256
+ self.view,
257
+ "Not Enough Endonucleases",
258
+ "There are not enough endonucleases with this organism. At least 2 endonucleases are required for this function."
259
+ )
260
  return
261
+
262
+ # Get cotargeting controller and launch, passing self as view_targets_controller
263
+ cotargeting_controller = self.settings.get_cotargeting_window(self)
264
+ cotargeting_controller.launch(
265
+ endo_choices=current_items,
266
+ org_name=self.organism
267
+ )
268
+
269
  except Exception as e:
270
+ self.logger.error(f"Error in perform_cotargeting: {str(e)}")
271
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
272
+ show_error(self.settings, "Error", f"Could not launch co-targeting: {str(e)}")
273
 
274
  def highlight_gene_viewer(self):
275
+ """Highlight selected guides in gene viewer"""
276
  try:
277
+ self.logger.debug("Starting highlight_gene_viewer")
278
 
279
+ # Get selected guides
280
+ selected_rows = self.view.get_selected_guides()
281
+ self.logger.debug(f"Selected guides: {selected_rows}")
282
 
283
  if not selected_rows:
284
  QMessageBox.warning(self.view, "No Selection",
285
+ "Please select guides to highlight in the gene viewer.")
286
  return
287
 
288
  # Convert table selections to the format expected by the model
289
+ guides_to_highlight = []
290
+ for guide in selected_rows:
291
+ guide_info = {
292
+ 'location': guide['location'],
293
+ 'sequence': guide['sequence'],
294
+ 'strand': guide['strand']
295
  }
296
+ guides_to_highlight.append(guide_info)
297
+ self.logger.debug(f"Guide to highlight: {guide_info}")
298
 
299
  # Get current gene sequence
300
  current_gene = self.view.combo_box_gene.currentText()
 
 
301
 
302
+ # Check if this is a position-based search
303
+ if "chrom" in current_gene and "start:" in current_gene:
304
+ # Parse position from the text (format: "chrom X, start: Y, end: Z")
305
+ try:
306
+ parts = current_gene.split(',')
307
+ chrom = int(parts[0].split('chrom')[1].strip())
308
+ start = int(parts[1].split('start:')[1].strip())
309
+ end = int(parts[2].split('end:')[1].strip())
310
+
311
+ # Get sequence directly from model's _get_sequence_for_position
312
+ sequence = self.model._get_sequence_for_position(chrom, start, end)
313
+ if not sequence:
314
+ raise ValueError("Could not get sequence for position")
315
+
316
+ sequence_data = {
317
+ 'sequence': sequence,
318
+ 'start': start,
319
+ 'end': end
320
+ }
321
+ self.logger.debug(f"Got position-based sequence of length: {len(sequence)}")
322
+ except Exception as e:
323
+ self.logger.error(f"Error getting position sequence: {str(e)}")
324
+ QMessageBox.warning(self.view, "Error",
325
+ "Could not get sequence for the specified position.")
326
+ return
327
+ else:
328
+ # Regular gene-based search
329
+ locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
330
+ self.logger.debug(f"Getting sequence for locus tag: {locus_tag}")
331
+
332
+ # Get gene sequence with padding
333
+ sequence_data = self.model.get_gene_sequence(locus_tag)
334
+ if not sequence_data or 'sequence' not in sequence_data:
335
+ self.logger.error("No sequence data found")
336
+ QMessageBox.warning(self.view, "No Gene Data",
337
+ "Could not get gene sequence for highlighting.")
338
+ return
339
 
340
+ self.logger.debug(f"Gene sequence length: {len(sequence_data['sequence'])}")
341
 
342
  # Highlight the sequences
343
+ if guides_to_highlight:
344
+ self.logger.debug("Attempting to highlight sequences")
345
+ self.highlight_guides_in_gene_viewer(guides_to_highlight)
346
  else:
347
+ self.logger.error("No valid guides to highlight")
348
+ QMessageBox.warning(self.view, "No Valid Guides",
349
  "Could not get sequence information from the selected rows.")
350
 
351
  except Exception as e:
352
+ self.logger.error(f"Error in highlight_gene_viewer: {str(e)}\n{traceback.format_exc()}")
353
+ show_error(self.settings, "Error highlighting gene viewer", str(e))
354
 
355
  def export_targets(self):
356
  try:
357
+ # Get selected guides
358
+ selected_guides = self.view.get_selected_guides()
359
+ if not selected_guides:
360
+ QtWidgets.QMessageBox.warning(
361
+ self.view,
362
+ "No Selection",
363
+ "Please select guides to export."
364
+ )
365
  return
 
 
 
 
 
 
366
 
367
+ export_controller = self.settings.get_export_selected_grnas_window()
368
+ export_controller.show_dialog(selected_guides, "View Targets")
 
 
 
 
 
 
369
  except Exception as e:
370
+ self.logger.error(f"Error in export_targets: {str(e)}")
371
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
372
+ show_error(self.settings, "Export Error", str(e))
373
 
374
  def show_scoring_options(self):
 
375
  try:
376
  # Create scoring options controller if not exists
377
  if not hasattr(self, '_scoring_options_controller'):
378
  # Create controller with self as view_targets_controller
379
  self._scoring_options_controller = ScoringOptionsController(
380
+ global_settings=self.settings,
381
  view_targets_controller=self
382
  )
383
 
 
384
  self._scoring_options_controller.show()
385
 
386
  except Exception as e:
387
+ self.logger.error(f"Error showing scoring options: {str(e)}")
388
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
389
+ show_error(self.settings, "Error", f"Could not show scoring options: {str(e)}")
390
 
391
  def change_indices(self):
392
+ """Change the start and end positions for gene viewer"""
393
  try:
394
+ # Make sure gene viewer has content
395
+ if not self.view.text_edit_gene_viewer.toPlainText():
396
+ QMessageBox.warning(
397
+ self.view,
398
+ "Gene Viewer Error",
399
+ "Gene Viewer display is empty! Please ensure there is sequence data to view."
400
+ )
401
+ return
402
+
403
+ # Get current gene/position info
404
+ current_gene = self.view.combo_box_gene.currentText()
405
+
406
+ try:
407
+ new_start = int(self.view.line_edit_start_location.text())
408
+ new_end = int(self.view.line_edit_stop_location.text())
409
+ except ValueError:
410
+ QMessageBox.warning(
411
+ self.view,
412
+ "Invalid Input",
413
+ "Please enter valid integer values for start and end positions."
414
+ )
415
+ return
416
+
417
+ # Validate start and end positions
418
+ if new_start <= 0 or new_end <= 0:
419
+ QMessageBox.warning(
420
+ self.view,
421
+ "Invalid location indices",
422
+ "Location indices cannot be negative or zero! Please set values larger than 0."
423
+ )
424
+ return
425
+
426
+ if new_start >= new_end:
427
+ QMessageBox.warning(
428
+ self.view,
429
+ "Invalid location indices",
430
+ "Start location must be less than stop location."
431
+ )
432
+ return
433
+
434
+ if abs(new_start - new_end) > 50000:
435
+ QMessageBox.warning(
436
+ self.view,
437
+ "Sequence Too Long",
438
+ "The sequence is too long! Please choose indices that will make the sequence less than 50,000!"
439
+ )
440
+ return
441
+
442
+ # Get sequence for new range
443
+ if "chrom" in current_gene and "start:" in current_gene:
444
+ # For position-based searches
445
+ try:
446
+ parts = current_gene.split(',')
447
+ chrom = int(parts[0].split('chrom')[1].strip())
448
+
449
+ # Get sequence for new range
450
+ sequence = self.model._get_sequence_for_position(chrom, new_start, new_end)
451
+
452
+ if sequence:
453
+ self.view.set_text_edit_gene_viewer(sequence)
454
+ # Update the line edits with new positions
455
+ self.view.line_edit_start_location.setText(str(new_start))
456
+ self.view.line_edit_stop_location.setText(str(new_end))
457
+ else:
458
+ raise ValueError("Could not get sequence for new position")
459
+
460
+ except Exception as e:
461
+ QMessageBox.warning(
462
+ self.view,
463
+ "Position Error",
464
+ f"Error changing position: {str(e)}"
465
+ )
466
+ return
467
  else:
468
+ # For feature-based searches
469
+ locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
470
+ gene_data = self.model.get_gene_data(locus_tag)
471
+
472
+ if not gene_data or 'info' not in gene_data:
473
+ QMessageBox.warning(
474
+ self.view,
475
+ "Gene Data Error",
476
+ "Could not get gene data for the current selection."
477
+ )
478
+ return
479
+
480
+ # Get new sequence for the range
481
+ sequence_data = self.model.get_gene_sequence_for_range(locus_tag, new_start, new_end)
482
+ if sequence_data:
483
+ self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
484
+ # Update the line edits with new positions
485
+ self.view.line_edit_start_location.setText(str(new_start))
486
+ self.view.line_edit_stop_location.setText(str(new_end))
487
+ else:
488
+ QMessageBox.warning(
489
+ self.view,
490
+ "Sequence Error",
491
+ "Could not get sequence for the specified range."
492
+ )
493
+ return
494
+
495
  except Exception as e:
496
+ self.logger.error(f"Error in change_indices: {str(e)}")
497
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
498
+ show_error(self.settings, "Error changing indices", str(e))
499
 
500
  def reset_location(self):
501
+ """Reset gene viewer to the original sequence and location"""
502
  try:
503
+ # Get current gene/position
504
+ current_gene = self.view.combo_box_gene.currentText()
505
+
506
+ # Check if this is a position-based search
507
+ if "chrom" in current_gene and "start:" in current_gene:
508
+ try:
509
+ # Parse position from the text (format: "chrom X, start: Y, end: Z")
510
+ parts = current_gene.split(',')
511
+ chrom = int(parts[0].split('chrom')[1].strip())
512
+ start = int(parts[1].split('start:')[1].strip())
513
+ end = int(parts[2].split('end:')[1].strip())
514
+
515
+ # Get sequence directly using model's method
516
+ sequence = self.model._get_sequence_for_position(chrom, start, end)
517
+ if sequence:
518
+ # Update gene viewer with sequence
519
+ self.view.set_text_edit_gene_viewer(sequence)
520
+
521
+ # Update location fields
522
+ self.view.line_edit_start_location.setText(str(start))
523
+ self.view.line_edit_stop_location.setText(str(end))
524
+ else:
525
+ raise ValueError("Could not get sequence for position")
526
+
527
+ except Exception as e:
528
+ self.logger.error(f"Error resetting position: {str(e)}")
529
+ QMessageBox.warning(
530
+ self.view,
531
+ "Position Error",
532
+ f"Error resetting position: {str(e)}"
533
+ )
534
+ return
535
+ else:
536
+ # For feature-based searches
537
+ locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
538
+ sequence_data = self.model.get_gene_sequence(locus_tag)
539
+
540
+ if sequence_data:
541
+ # Update gene viewer with sequence
542
+ self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
543
+
544
+ # Update location fields
545
+ self.view.line_edit_start_location.setText(str(sequence_data['start']))
546
+ self.view.line_edit_stop_location.setText(str(sequence_data['end']))
547
+ else:
548
+ self.logger.warning(f"No sequence data found for locus tag {locus_tag}")
549
+ QMessageBox.warning(
550
+ self.view,
551
+ "Gene Data Error",
552
+ "Could not get gene sequence for resetting location."
553
+ )
554
+ return
555
+
556
  except Exception as e:
557
+ self.logger.error(f"Error in reset_location: {str(e)}")
558
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
559
+ show_error(self.settings, "Error resetting location", str(e))
560
 
561
  def select_all(self, state):
562
  try:
563
+ self.view.select_all_guides(state == 2) # 2 corresponds to Qt.Checked
564
  except Exception as e:
565
+ show_error(self.settings, "Error selecting all guides", str(e))
566
 
567
  def display_gene_data(self, gene_name):
568
  try:
569
  gene_data = self.model.get_gene_data(gene_name)
570
+ print(f"Gene data: {gene_data}")
571
  if gene_data and gene_data['sequence']:
572
  self.view.set_text_edit_gene_viewer(gene_data['sequence'])
573
  self.view.line_edit_start_location.setText(str(gene_data['start']))
 
577
  self.view.line_edit_start_location.clear()
578
  self.view.line_edit_stop_location.clear()
579
  except Exception as e:
580
+ show_error(self.settings, "Error displaying gene data", str(e))
581
 
582
+ def refresh_guides_display(self):
583
+ """Refresh the guides display when filters change"""
584
  try:
585
+ if hasattr(self, 'selected_targets'):
586
+ # Get current guides from model
587
+ self.model.load_guides(self.selected_targets, self.organism, self.endonuclease)
588
+ guides = self.model.get_guides()
589
+ self.view.display_guides_in_table(guides)
590
  except Exception as e:
591
+ show_error(self.settings, "Error refreshing guides display", str(e))
592
 
593
  def show(self):
594
  self.view.show()
 
596
  def on_gene_selected(self, selected_text):
597
  """Handle gene selection signal"""
598
  try:
599
+ self.logger.debug(f"Gene selection changed to: {selected_text}")
600
+
601
+ # Check if this is a position-based search
602
+ if "chrom" in selected_text and "start:" in selected_text:
603
+ try:
604
+ # Parse position from the text (format: "chrom X, start: Y, end: Z")
605
+ parts = selected_text.split(',')
606
+ chrom = int(parts[0].split('chrom')[1].strip())
607
+ start = int(parts[1].split('start:')[1].strip())
608
+ end = int(parts[2].split('end:')[1].strip())
609
+
610
+ # Get sequence directly using _get_sequence_for_position
611
+ sequence = self.model._get_sequence_for_position(chrom, start, end)
612
+ if sequence:
613
+ # Update gene viewer with sequence
614
+ self.view.set_text_edit_gene_viewer(sequence)
615
+
616
+ # Update location fields
617
+ self.view.line_edit_start_location.setText(str(start))
618
+ self.view.line_edit_stop_location.setText(str(end))
619
+
620
+ self.logger.debug(f"Updated position view with sequence of length: {len(sequence)}")
621
+
622
+ # Filter guides for this position
623
+ position_guides = [g for g in self.model.guides
624
+ if g.get('feature_id') == selected_text]
625
+ self.view.display_guides_in_table(position_guides)
626
+ else:
627
+ self.logger.warning(f"No sequence found for position {chrom}:{start}-{end}")
628
+ self.view.set_text_edit_gene_viewer("No sequence data available for this position")
629
+ self.view.line_edit_start_location.clear()
630
+ self.view.line_edit_stop_location.clear()
631
+ except Exception as e:
632
+ self.logger.error(f"Error handling position selection: {str(e)}")
633
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
634
  else:
635
+ # Regular gene-based search
636
+ locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
637
+ self.logger.debug(f"Loading sequence for locus tag: {locus_tag}")
638
+
639
+ # Get gene sequence with padding using locus tag
640
+ sequence_data = self.model.get_gene_sequence(locus_tag)
641
+ if sequence_data:
642
+ # Update gene viewer with sequence
643
+ self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
644
+
645
+ # Update location fields
646
+ self.view.line_edit_start_location.setText(str(sequence_data['start']))
647
+ self.view.line_edit_stop_location.setText(str(sequence_data['end']))
648
+
649
+ self.logger.debug(f"Updated gene viewer with sequence of length: {len(sequence_data['sequence'])}")
650
+
651
+ # Filter guides for this gene
652
+ gene_guides = [g for g in self.model.guides
653
+ if str(g.get('feature_id', '')).strip().lower() == locus_tag.lower()]
654
+ self.view.display_guides_in_table(gene_guides)
655
+ else:
656
+ self.logger.warning(f"No sequence data found for locus tag {locus_tag}")
657
+ self.view.set_text_edit_gene_viewer("No sequence data available for this gene")
658
+ self.view.line_edit_start_location.clear()
659
+ self.view.line_edit_stop_location.clear()
660
 
661
  except Exception as e:
662
+ self.logger.error(f"Error handling gene selection: {str(e)}")
663
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
664
 
665
+ def highlight_guides_in_gene_viewer(self, guides_to_highlight=None):
666
+ """Highlight selected guides in gene viewer"""
667
  try:
668
+ self.logger.debug("Starting highlight_gene_viewer")
669
 
670
+ if guides_to_highlight is None:
671
+ guides_to_highlight = self.view.get_selected_guides()
 
672
 
673
+ self.logger.debug(f"Selected guides: {guides_to_highlight}")
674
 
675
+ if not guides_to_highlight:
676
  QMessageBox.warning(self.view, "No Selection",
677
+ "Please select guides to highlight in the gene viewer.")
678
  return
679
 
680
  # Get current gene sequence
681
  selected_text = self.view.combo_box_gene.currentText()
 
682
 
683
+ # For position-based searches, get sequence directly from model
684
+ if "chrom" in selected_text and "start:" in selected_text:
685
+ try:
686
+ # Parse position from the text (format: "chrom X, start: Y, end: Z")
687
+ parts = selected_text.split(',')
688
+ chrom = int(parts[0].split('chrom')[1].strip())
689
+ start = int(parts[1].split('start:')[1].strip())
690
+ end = int(parts[2].split('end:')[1].strip())
691
+
692
+ # Get sequence directly from FindTargetsModel
693
+ sequence = self.model._get_sequence_for_position(chrom, start, end)
694
+ if not sequence:
695
+ raise ValueError("Could not get sequence for position")
696
+
697
+ self.logger.debug(f"Got sequence of length {len(sequence)} for position-based search")
698
+
699
+ except Exception as e:
700
+ self.logger.error(f"Error parsing position or getting sequence: {str(e)}")
701
+ return
702
+ else:
703
+ # Regular gene-based search
704
+ locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
705
+ sequence_data = self.model.get_gene_sequence(locus_tag)
706
+ if not sequence_data or 'sequence' not in sequence_data:
707
+ self.logger.error("No sequence data available for highlighting")
708
+ return
709
+ sequence = sequence_data['sequence']
710
 
711
+ # Process highlights
 
 
712
  highlights = []
713
  sequences_found = 0
714
+ total_sequences = len(guides_to_highlight)
715
 
716
+ for guide in guides_to_highlight:
717
+ self.logger.debug(f"Processing guide: {guide}")
718
+ sequence_to_find = guide['sequence']
719
+ strand = guide['strand']
720
 
 
721
  if strand == '-':
722
  sequence_to_find = str(Seq(sequence_to_find).reverse_complement())
723
+ self.logger.debug(f"Reverse complemented sequence: {sequence_to_find}")
724
 
 
725
  sequence_upper = sequence.upper()
726
  target_upper = sequence_to_find.upper()
727
 
728
+ self.logger.debug(f"Searching for sequence: {target_upper}")
729
 
 
730
  pos = sequence_upper.find(target_upper)
731
  if pos != -1:
732
+ self.logger.debug(f"Found sequence at position: {pos}")
733
  color = 'red' if strand == '-' else 'green'
734
  highlights.append((pos, len(sequence_to_find), color))
735
  sequences_found += 1
736
  else:
737
+ self.logger.debug(f"Sequence not found: {target_upper}")
738
 
 
739
  if sequences_found == 0:
740
+ self.logger.warning("No sequences could be highlighted")
741
  QMessageBox.warning(self.view, "Highlighting Failed",
742
  "Could not highlight any of the selected sequences in the current gene view.")
743
  return
744
 
 
 
745
  # Build highlighted sequence
746
  result = []
747
  last_pos = 0
748
+ for pos, length, color in sorted(highlights):
749
  result.append(sequence[last_pos:pos])
750
  result.append(f"<span style='background-color: {color};'>")
751
  result.append(sequence[pos:pos+length])
 
757
 
758
  # Update the view with highlighted sequence
759
  self.view.update_gene_viewer(highlighted_sequence)
760
+ self.logger.debug(f"Successfully highlighted {sequences_found} sequences")
761
 
762
  except Exception as e:
763
+ self.logger.error(f"Error highlighting guides: {str(e)}")
764
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
765
 
766
  def update_scores(self, scores, algorithm):
767
  """Update the table with new scores from alternative scoring methods"""
 
770
  headers = self.view.get_table_headers()
771
 
772
  # Get selected rows
773
+ selected_rows = sorted(set(index.row() for index in self.view.table_guides.selectedIndexes()))
774
  if not selected_rows:
775
+ self.logger.warning("No rows selected for scoring")
776
  return
777
 
778
  # Determine the position for the new column (after the "Score" column)
 
780
  desired_index = score_index + 1
781
 
782
  # Disable updates to prevent crashes
783
+ self.view.table_guides.setUpdatesEnabled(False)
784
 
785
  try:
786
  # Add new column for algorithm if it doesn't exist
787
  if algorithm not in headers:
 
 
788
 
789
  # Insert new column after Score
790
+ self.view.table_guides.insertColumn(desired_index)
791
 
792
  # Set header for new column
793
+ self.view.table_guides.setHorizontalHeaderItem(
794
  desired_index,
795
  QtWidgets.QTableWidgetItem(algorithm)
796
  )
797
 
798
  # Move Off-Target and Details columns one position right
799
+ for row in range(self.view.table_guides.rowCount()):
800
  # Move Off-Target
801
+ off_target_item = self.view.table_guides.takeItem(row, desired_index)
802
  if off_target_item:
803
+ self.view.table_guides.setItem(row, desired_index + 1, off_target_item)
804
 
805
  # Move Details button
806
+ details_widget = self.view.table_guides.cellWidget(row, desired_index)
807
  if details_widget:
808
+ self.view.table_guides.setCellWidget(row, desired_index + 1, details_widget)
809
 
810
  col_index = desired_index
811
  else:
 
818
  # Round to 2 decimal places
819
  rounded_score = round(float(scores[score_idx]), 2)
820
  score_item.setData(QtCore.Qt.ItemDataRole.EditRole, rounded_score)
821
+ self.view.table_guides.setItem(row, col_index, score_item)
822
 
823
+ # Also update the guide data to preserve score during filtering/sorting
824
  if hasattr(self.view, '_all_results'):
825
  self.view._all_results[row]['azimuth_score'] = rounded_score
826
 
827
  # Resize columns to fit new content
828
+ self.view.table_guides.resizeColumnsToContents()
829
 
830
+ self.logger.debug(f"Updated scores for algorithm: {algorithm}")
831
+ self.logger.debug(f"Updated rows: {selected_rows}")
832
 
833
  finally:
834
  # Re-enable updates
835
+ self.view.table_guides.setUpdatesEnabled(True)
836
 
837
  except Exception as e:
838
+ self.logger.error(f"Error updating scores: {str(e)}")
839
  raise
840
+
841
+ def clear_highlighted_guides(self):
842
+ """Clear highlights in gene viewer and unselect rows in target table"""
843
+ try:
844
+ # Get current gene/position
845
+ current_gene = self.view.combo_box_gene.currentText()
846
+
847
+ # Reset gene viewer to original sequence
848
+ if "chrom" in current_gene and "start:" in current_gene:
849
+ # For position-based searches
850
+ try:
851
+ parts = current_gene.split(',')
852
+ chrom = int(parts[0].split('chrom')[1].strip())
853
+ start = int(parts[1].split('start:')[1].strip())
854
+ end = int(parts[2].split('end:')[1].strip())
855
+
856
+ sequence = self.model._get_sequence_for_position(chrom, start, end)
857
+ if sequence:
858
+ self.view.set_text_edit_gene_viewer(sequence)
859
+ except Exception as e:
860
+ self.logger.error(f"Error resetting position sequence: {str(e)}")
861
+ else:
862
+ # For feature-based searches
863
+ locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
864
+ sequence_data = self.model.get_gene_sequence(locus_tag)
865
+ if sequence_data and 'sequence' in sequence_data:
866
+ self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
867
+
868
+ # Clear selected rows in table
869
+ self.view.table_guides.clearSelection()
870
+
871
+ # Uncheck select all checkbox
872
+ self.view.check_box_select_all.setChecked(False)
873
+
874
+ self.logger.debug("Cleared highlighted guides and selections")
875
+
876
+ except Exception as e:
877
+ self.logger.error(f"Error clearing highlighted guides: {str(e)}")
878
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
879
+ show_error(self.settings, "Error clearing guides", str(e))
880
+
881
+ def handle_cotargeting_result(self, selected_endos):
882
+ try:
883
+ # Format combined endonuclease string
884
+ endo_string = "|".join(selected_endos)
885
+
886
+ # Add combined choice to endonuclease combo box
887
+ current_items = [self.view.combo_box_endonuclease.itemText(i)
888
+ for i in range(self.view.combo_box_endonuclease.count())]
889
+
890
+ if endo_string not in current_items:
891
+ self.view.combo_box_endonuclease.insertItem(0, endo_string)
892
+
893
+ # Set as current selection
894
+ self.view.combo_box_endonuclease.setCurrentText(endo_string)
895
+
896
+ # Define PAM compatibility rules
897
+ pam_rules = {
898
+ 'SpCas9': 'NGG',
899
+ 'SaCas9': 'NNGRRT',
900
+ 'NmCas9': 'NNNNGATT',
901
+ 'St1Cas9': 'NNAGAAW',
902
+ 'St3Cas9': 'NGGNG',
903
+ 'TdCas9': 'NAAAAC',
904
+ 'CjCas9': 'NNNNRYAC'
905
+ }
906
+
907
+ # Function to check if a PAM sequence matches the required pattern
908
+ def is_pam_compatible(pam_seq, pattern):
909
+ if len(pam_seq) != len(pattern):
910
+ return False
911
+
912
+ for p, t in zip(pam_seq.upper(), pattern.upper()):
913
+ if t == 'N':
914
+ continue
915
+ elif t == 'R' and p not in 'AG':
916
+ return False
917
+ elif t == 'Y' and p not in 'CT':
918
+ return False
919
+ elif t == 'W' and p not in 'AT':
920
+ return False
921
+ elif t != p and t != 'N':
922
+ return False
923
+ return True
924
+
925
+ # Get guides for each individual endonuclease
926
+ all_guides = []
927
+ for endo in selected_endos:
928
+ # Update guides with current endonuclease
929
+ updated_guides = []
930
+ for guide in self.selected_targets:
931
+ new_guide = guide.copy()
932
+ new_guide['endonuclease'] = endo
933
+
934
+ # Extract chromosome number from full identifier
935
+ if 'chromosome' in new_guide:
936
+ chrom_id = new_guide['chromosome']
937
+ if isinstance(chrom_id, str) and '.' in chrom_id:
938
+ chrom_num = chrom_id.split('.')[-1]
939
+ new_guide['chromosome'] = chrom_num
940
+
941
+ updated_guides.append(new_guide)
942
+
943
+ # Load guides for this endonuclease
944
+ self.model.load_guides(updated_guides, self.organism, endo)
945
+ guides = self.model.get_guides()
946
+ all_guides.extend(guides)
947
+
948
+ # Group guides by sequence and collect their PAMs
949
+ sequence_groups = {}
950
+ for guide in all_guides:
951
+ seq = guide['sequence']
952
+ if seq not in sequence_groups:
953
+ sequence_groups[seq] = {'guides': [], 'pams': set(), 'endos': set()}
954
+ sequence_groups[seq]['guides'].append(guide)
955
+ sequence_groups[seq]['pams'].add(guide['pam'])
956
+ sequence_groups[seq]['endos'].add(guide['endonuclease'])
957
+
958
+ # Filter for guides that have compatible PAMs across all endonucleases
959
+ cotargeted_guides = []
960
+ for seq_info in sequence_groups.values():
961
+ if len(seq_info['endos']) == len(selected_endos):
962
+ # Get the most stringent PAM from the guides
963
+ guide_pams = list(seq_info['pams'])
964
+
965
+ # Sort PAMs by length (longer PAMs are typically more stringent)
966
+ guide_pams.sort(key=len, reverse=True)
967
+ stringent_pam = guide_pams[0]
968
+
969
+ # Check if this PAM is compatible with all selected endonucleases
970
+ is_compatible = True
971
+ for endo in selected_endos:
972
+ for cas9_type, pam_pattern in pam_rules.items():
973
+ if cas9_type in endo:
974
+ if not is_pam_compatible(stringent_pam, pam_pattern):
975
+ is_compatible = False
976
+ break
977
+ if not is_compatible:
978
+ break
979
+
980
+ if is_compatible:
981
+ # Use the guide with the most stringent PAM
982
+ for guide in seq_info['guides']:
983
+ if guide['pam'] == stringent_pam:
984
+ combined_guide = guide.copy()
985
+ combined_guide['endonuclease'] = endo_string
986
+ cotargeted_guides.append(combined_guide)
987
+ break
988
+
989
+ # Update display with co-targeted guides
990
+ self.view.display_guides_in_table(cotargeted_guides)
991
+
992
+ self.logger.debug(f"Found {len(cotargeted_guides)} co-targeted guides with compatible PAMs")
993
+
994
+ except Exception as e:
995
+ self.logger.error(f"Error handling co-targeting result: {str(e)}")
996
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
997
+ show_error(self.settings, "Co-targeting Error", str(e))
src/controllers/populationAnalysis.py DELETED
@@ -1,929 +0,0 @@
1
- from PyQt5 import QtWidgets, Qt, QtGui, QtCore, uic
2
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
3
- from matplotlib.figure import Figure
4
- import models.GlobalSettings as GlobalSettings
5
- import os
6
- import utils.sequence_utils as sequence_utils
7
- import numpy as np
8
- from PyQt5.QtWidgets import *
9
- import gzip
10
- import sqlite3
11
- import itertools
12
- import matplotlib.patches as patches
13
- import mplcursors
14
- import copy
15
- from utils.ui import show_error, scale_ui, center_ui, show_message
16
-
17
- logger = GlobalSettings.logger
18
-
19
- class Pop_Analysis(QtWidgets.QMainWindow):
20
- def __init__(self):
21
- try:
22
- super(Pop_Analysis, self).__init__()
23
- uic.loadUi(GlobalSettings.appdir + 'ui/pop.ui', self)
24
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
25
- self.goBackButton.clicked.connect(self.go_back)
26
- self.analyze_button.clicked.connect(self.pre_analyze)
27
- self.clear_Button.clicked.connect(self.clear)
28
- self.export_button.clicked.connect(self.export_tool)
29
- self.sq = sequence_utils.SeqTranslate()
30
- self.ref_para_list = list()
31
- self.mode = 0
32
- self.find_locs_button.clicked.connect(self.find_locations)
33
- self.clear_loc_button.clicked.connect(self.clear_loc_table)
34
- self.names = []
35
- self.names_venn = []
36
-
37
- """ Colormap Graph initialization """
38
- self.colormap_layout = QtWidgets.QVBoxLayout()
39
- self.colormap_layout.setContentsMargins(0,0,0,0)
40
- self.colormap_canvas = MplCanvas(self) ###Initialize new Canvas
41
-
42
- groupbox_style = """
43
- QGroupBox:title{subcontrol-origin: margin;
44
- left: 10px;
45
- padding: 0 5px 0 15px;}
46
- QGroupBox#groupBox{border: 2px solid rgb(111,181,110);
47
- border-radius: 9px;
48
- font: bold 14pt 'Arial';
49
- margin-top: 10px;}"""
50
- self.groupBox.setStyleSheet(groupbox_style)
51
- self.groupBox_2.setStyleSheet(groupbox_style.replace("groupBox","groupBox_2"))
52
-
53
- #organism table
54
- self.org_Table.setColumnCount(1)
55
- self.org_Table.setShowGrid(False)
56
- self.org_Table.setHorizontalHeaderLabels(["Organism"])
57
- self.org_Table.horizontalHeader().setSectionsClickable(True)
58
- self.org_Table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
59
- self.org_Table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
60
- self.org_Table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
61
- self.org_Table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
62
- #self.org_Table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
63
-
64
- #top-right table
65
- self.table2.setColumnCount(9)
66
- self.table2.setShowGrid(False)
67
- self.table2.setHorizontalHeaderLabels(["Seed","% Coverage","Total Repeats","Avg. Repeats/Scaffold", "Consensus Sequence", "% Consensus", "Score","PAM", "Strand"])
68
- self.table2.horizontalHeader().setSectionsClickable(True)
69
- self.table2.horizontalHeader().sectionClicked.connect(self.table2_sorting)
70
- self.table2.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
71
- self.table2.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
72
- self.table2.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
73
- self.table2.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
74
- self.table2.resizeColumnsToContents()
75
-
76
- # Finder table
77
- self.loc_finder_table.setColumnCount(5)
78
- self.loc_finder_table.setShowGrid(False)
79
- self.loc_finder_table.setHorizontalHeaderLabels(["Seed ID", "Sequence", "Organism", "Scaffold", "Location"])
80
- self.loc_finder_table.horizontalHeader().setSectionsClickable(True)
81
- self.loc_finder_table.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
82
- self.loc_finder_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
83
- self.loc_finder_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
84
- #self.loc_finder_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
85
- # self.loc_finder_table.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
86
- # self.loc_finder_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
87
- self.loc_finder_table.resizeColumnsToContents()
88
-
89
- #custom seed search
90
- self.query_seed_button.clicked.connect(self.custom_seed_search)
91
-
92
- self.total_org_number = 0
93
-
94
- self.switcher_table2 = [1, 1, 1, 1, 1, 1, 1, 1,
95
- 1] # for keeping track of where we are in the sorting clicking for each column
96
- self.switcher_loc_table = [1, 1, 1, 1, 1]
97
-
98
- #Initialize variables
99
- self.index_to_cspr = {}
100
- self.index_to_db = {}
101
- self.name_to_db = {}
102
- self.cspr_files = []
103
- self.seeds = []
104
-
105
- self.loading_window = loading_window()
106
-
107
- self.first_show = True
108
- scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30)
109
-
110
- except Exception as e:
111
- show_error("Error initializing population analysis.", e)
112
-
113
- #export shared seed table to csv function
114
- def export_tool(self):
115
- try:
116
- select_items = self.table2.selectedItems()
117
-
118
- if len(select_items) <= 0:
119
- show_message(
120
- fontSize=12,
121
- icon=QtWidgets.QMessageBox.Icon.Critical,
122
- title="Nothing Selected",
123
- message="No targets were highlighted. Please highlight the targets you want to be exported to a CSV File!"
124
- )
125
- return
126
-
127
- GlobalSettings.mainWindow.export_tool_window.launch(select_items,"pa")
128
- except Exception as e:
129
- show_error("Error in export_tool() in population analysis.", e)
130
-
131
- # this function calls the popParser function and fills all the tables
132
- def pre_analyze(self):
133
- try:
134
- #clear saved filenames
135
- self.cspr_files = []
136
- self.db_files = []
137
- self.rows = []
138
-
139
- #get selected indexes
140
- selected_indexes = self.org_Table.selectionModel().selectedRows()
141
-
142
- if len(selected_indexes) == 0:
143
- show_message(
144
- fontSize=12,
145
- icon=QtWidgets.QMessageBox.Icon.Critical,
146
- title="Error",
147
- message="Please select CSPR file(s) for analysis."
148
- )
149
- return
150
-
151
- #get cspr and db filenames
152
- for index in sorted(selected_indexes):
153
- self.rows.append(index.row() + 1)
154
- self.cspr_files.append(self.index_to_cspr[index.row()])
155
- self.db_files.append(self.index_to_db[index.row()])
156
-
157
- self.get_org_names()
158
- self.fill_data()
159
- except Exception as e:
160
- show_error("Error in pre_analyze() in population analysis.", e)
161
-
162
- def launch(self):
163
- try:
164
- self.get_data()
165
- except Exception as e:
166
- show_error("Error in launch() in population analysis.", e)
167
-
168
- #responsible for calling all loading/analysis functions for loading the shared seeds table and generating the heatmap based on selected organisms
169
- def get_data(self):
170
- try:
171
- self.fillEndo()
172
- except Exception as e:
173
- show_error("Error in get_data() in population analysis.", e)
174
-
175
- # this function opens CASPERinfo and builds the dropdown menu of selectable endonucleases
176
- def fillEndo(self):
177
- try:
178
- try:
179
- self.endoBox.currentIndexChanged.disconnect()
180
- except:
181
- pass
182
-
183
- self.Endos = {}
184
- self.endoBox.clear()
185
- f = open(GlobalSettings.appdir + 'CASPERinfo')
186
- while True:
187
- line = f.readline()
188
- if line.startswith('ENDONUCLEASES'):
189
- while True:
190
- line = f.readline()
191
- if (line[0] == "-"):
192
- break
193
- line_tokened = line.split(";")
194
- endo = line_tokened[0]
195
- # Checking to see if there is more than one pam sequence in the list
196
- if line_tokened[1].find(",") != -1:
197
- p_pam = line_tokened[1].split(",")[0]
198
- else:
199
- p_pam = line_tokened[1]
200
- default_five_length = line_tokened[2]
201
- default_seed_length = line_tokened[3]
202
- default_three_length = line_tokened[4]
203
- self.Endos[endo + " PAM: " + p_pam] = (endo, p_pam, default_five_length, default_seed_length, default_three_length)
204
-
205
- break
206
- f.close()
207
- self.endoBox.addItems(self.Endos.keys())
208
- self.endoBox.currentIndexChanged.connect(self.change_endo)
209
- self.change_endo()
210
- except Exception as e:
211
- show_error("Error in fillEndo() in population analysis.", e)
212
-
213
- #event handler for updating the organism options based on the endo selected
214
- def change_endo(self):
215
- try:
216
- self.org_Table.clearContents()
217
- self.org_Table.setRowCount(0)
218
- onlyfiles = [f for f in os.listdir(GlobalSettings.CSPR_DB) if os.path.isfile(os.path.join(GlobalSettings.CSPR_DB, f))]
219
- index = 0
220
- for file in onlyfiles:
221
- if file.find('.cspr') != -1:
222
- endo = file[file.rfind('_') + 1:file.find('.cspr')]
223
- if endo == self.Endos[self.endoBox.currentText()][0]:
224
-
225
- # increase row count
226
- self.org_Table.setRowCount(index + 1)
227
-
228
- # open .cspr file and get genome name
229
- f = open(file, 'r')
230
- line = f.readline()
231
- f.close()
232
- line = str(line)
233
- line = line.split(":")[-1].strip()
234
- orgName = line
235
- # add genome name to table
236
- tabWidget = QtWidgets.QTableWidgetItem(orgName)
237
- tabWidget.setTextAlignment(QtCore.Qt.AlignVCenter)
238
- self.org_Table.setItem(index, 0, tabWidget)
239
-
240
- # store cspr and db file information for later
241
- self.index_to_cspr[index] = file
242
- db_file = file.replace(".cspr", "")
243
- db_file += "_repeats.db"
244
- self.index_to_db[index] = db_file
245
-
246
- # incrase row index
247
- index += 1
248
-
249
- if index == 0:
250
- self.org_Table.clearContents()
251
- self.org_Table.setRowCount(0)
252
-
253
- self.org_Table.resizeColumnsToContents()
254
- except Exception as e:
255
- show_error("Error in change_endo() in population analysis.", e)
256
-
257
- #fills shared seed table with data from analysis
258
- def fill_data(self):
259
- try:
260
- #update progress bar
261
- self.loading_window.loading_bar.setValue(5)
262
- center_ui(self.loading_window)
263
- self.loading_window.show()
264
- QtCore.QCoreApplication.processEvents()
265
-
266
- #prep table
267
- self.total_org_number = len(self.cspr_files)
268
- self.table2.setRowCount(0)
269
- self.loading_window.loading_bar.setValue(10)
270
- index = 0
271
-
272
- self.seeds = self.get_shared_seeds(self.db_files, True)
273
-
274
- try:
275
- os.remove(GlobalSettings.appdir + "temp_join.db")
276
- except:
277
- pass
278
-
279
- no_seeds = False
280
-
281
- if(len(self.seeds) == 0):
282
- no_seeds = True
283
-
284
- #QtCore.QCoreApplication.processEvents()
285
-
286
- #retrieve data on shared seeds
287
- if no_seeds == False:
288
- increase_val = float(15 / len(self.seeds))
289
- running_val = self.loading_window.loading_bar.value()
290
- self.loading_window.info_label.setText("Parsing Seed Data")
291
- self.counts = []
292
- for seed in self.seeds:
293
- # increase row count
294
- self.table2.setRowCount(index + 1)
295
-
296
- # push seed to table
297
- table_seed = QtWidgets.QTableWidgetItem()
298
- table_seed.setData(QtCore.Qt.EditRole, seed)
299
- table_seed.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
300
- self.table2.setItem(index, 0, table_seed)
301
-
302
- total_count = 0
303
- org_count = 0
304
- threes = []
305
- fives = []
306
- scores = []
307
- pams = []
308
- locs = []
309
-
310
- for db_file in self.db_files:
311
- conn = sqlite3.connect(db_file)
312
- c = conn.cursor()
313
- data = c.execute("SELECT count, three, five, pam, score, location FROM repeats WHERE seed = ? ",(seed,)).fetchone()
314
- if data != None:
315
- data = list(data)
316
- print(data)
317
- org_count += 1
318
- total_count += int(data[0])
319
- threes += data[1].split(",")
320
- fives += data[2].split(",")
321
- pams += data[3].split(",")
322
- scores += data[4].split(",")
323
- print(scores)
324
- locs += data[5].split(",")
325
-
326
- self.counts.append(total_count)
327
-
328
- if len(threes) < len(fives):
329
- for i in range(len(fives) - len(threes)):
330
- threes.append('')
331
-
332
- elif len(fives) < len(threes):
333
- for i in range(len(threes) - len(fives)):
334
- fives.append('')
335
-
336
- majority_index = 0
337
- three_prime, five_prime, both_prime = False, False, False
338
- if threes[0] == '':
339
- majority = max(set(fives), key=fives.count)
340
- majority_index = fives.index(majority)
341
- five_prime = True
342
- elif fives[0] == '':
343
- majority = max(set(threes), key=threes.count)
344
- majority_index = threes.index(majority)
345
- three_prime = True
346
- else:
347
- #account for both 3 and 5 present
348
- threes_and_fives = []
349
- for i in range(len(threes)):
350
- threes_and_fives.append(threes[i] + fives[i])
351
- majority = max(set(threes_and_fives), key=threes_and_fives.count)
352
- majority_index = threes_and_fives.index(majority)
353
- both_prime = True
354
-
355
- # push percent coverage
356
- perc_cov = QtWidgets.QTableWidgetItem()
357
- coverage = (org_count / len(self.db_files)) * 100
358
- coverage = float("%.2f" % coverage)
359
- perc_cov.setData(QtCore.Qt.EditRole, coverage)
360
- perc_cov.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
361
- self.table2.setItem(index, 1, perc_cov)
362
-
363
- # push total count
364
- table_count = QtWidgets.QTableWidgetItem()
365
- table_count.setData(QtCore.Qt.EditRole, total_count)
366
- table_count.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
367
- self.table2.setItem(index, 2, table_count)
368
-
369
- # push avg repeat
370
- avg_rep = QtWidgets.QTableWidgetItem()
371
- avg_rep_per_scaff = total_count / org_count
372
- avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
373
- avg_rep.setData(QtCore.Qt.EditRole, avg_rep_per_scaff)
374
- avg_rep.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
375
- self.table2.setItem(index, 3, avg_rep)
376
-
377
- # push seq
378
- seq = QtWidgets.QTableWidgetItem()
379
- seq.setData(QtCore.Qt.EditRole, fives[majority_index] + seed + threes[majority_index])
380
- seq.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
381
- self.table2.setItem(index, 4, seq)
382
-
383
- # push percent consensus
384
- perc_cons = QtWidgets.QTableWidgetItem()
385
- percent_consensus = 0
386
- if five_prime == True:
387
- percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
388
- elif three_prime == True:
389
- percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
390
- elif both_prime:
391
- percent_consensus = (threes_and_fives.count(threes_and_fives[majority_index]) / len(threes_and_fives)) * 100
392
-
393
- percent_consensus = float("%.2f" % percent_consensus)
394
- perc_cons.setData(QtCore.Qt.EditRole, percent_consensus)
395
- perc_cons.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
396
- self.table2.setItem(index, 5, perc_cons)
397
-
398
- # push score
399
- score = QtWidgets.QTableWidgetItem()
400
- score.setData(QtCore.Qt.EditRole, scores[majority_index])
401
- score.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
402
- self.table2.setItem(index, 6, score)
403
-
404
- # push PAM
405
- pam = QtWidgets.QTableWidgetItem()
406
- pam.setData(QtCore.Qt.EditRole, pams[majority_index])
407
- pam.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
408
- self.table2.setItem(index, 7, pam)
409
-
410
- # push strand
411
- strand_val = ""
412
- if int(locs[majority_index]) < 0:
413
- strand_val = "-"
414
- else:
415
- strand_val = "+"
416
-
417
- strand = QtWidgets.QTableWidgetItem()
418
- strand.setData(QtCore.Qt.EditRole, strand_val)
419
- strand.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
420
- self.table2.setItem(index, 8, strand)
421
-
422
- index += 1
423
- running_val += increase_val
424
- self.loading_window.loading_bar.setValue(int(running_val))
425
-
426
- self.table2.resizeColumnsToContents()
427
- self.loading_window.loading_bar.setValue(25)
428
- if len(self.db_files) > 1:
429
- self.plot_3D_graph()
430
- else:
431
- self.colormap_canvas.figure.set_visible(False)
432
-
433
- self.loading_window.loading_bar.setValue(100)
434
- self.loading_window.hide()
435
- QtCore.QCoreApplication.processEvents()
436
- except Exception as e:
437
- show_error("Error in fill_data() in population analysis.", e)
438
-
439
- #function to allow user to search for a specific seed amongst the organisms analyzed
440
- def custom_seed_search(self):
441
- try:
442
- seeds = str(self.seed_input.text())
443
- seeds = seeds.replace(" ","")
444
- seeds = seeds.upper()
445
- seeds = seeds.split(",")
446
-
447
- if len(seeds) == 1:
448
- if seeds[0] == "":
449
- self.pre_analyze()
450
- return
451
-
452
- # update progress bar
453
- self.loading_window.loading_bar.setValue(5)
454
- self.loading_window.show()
455
- QtCore.QCoreApplication.processEvents()
456
-
457
- # prep table
458
- self.total_org_number = len(self.cspr_files)
459
- self.loading_window.loading_bar.setValue(10)
460
- index = 0
461
-
462
- if (len(seeds) == 0):
463
- self.loading_window.hide()
464
- return
465
-
466
- if len(self.seeds) == 0:
467
- self.loading_window.hide()
468
- show_message(
469
- fontSize=12,
470
- icon=QtWidgets.QMessageBox.Icon.Critical,
471
- title="Error",
472
- message="No analysis has been run to be able to search for a specific seed."
473
- )
474
- return
475
-
476
- increase_val = float(15 / len(self.seeds))
477
- running_val = self.loading_window.loading_bar.value()
478
- self.loading_window.info_label.setText("Parsing Seed Data")
479
- # QtCore.QCoreApplication.processEvents()
480
-
481
- self.table2.setRowCount(0)
482
-
483
- # retrieve data on shared seeds
484
- self.counts = []
485
- for seed in seeds:
486
- total_count = 0
487
- org_count = 0
488
- threes = []
489
- fives = []
490
- scores = []
491
- pams = []
492
- locs = []
493
- none_data = True
494
- for db_file in self.db_files:
495
- conn = sqlite3.connect(db_file)
496
- c = conn.cursor()
497
- data = c.execute("SELECT count, three, five, pam, score, location FROM repeats WHERE seed = ?",
498
- (seed,)).fetchone()
499
- if data != None:
500
- none_data = False
501
- data = list(data)
502
- org_count += 1
503
- total_count += int(data[0])
504
- threes += data[1].split(",")
505
- fives += data[2].split(",")
506
- pams += data[3].split(",")
507
- scores += data[4].split(",")
508
- locs += data[5].split(",")
509
-
510
- if none_data == True:
511
- self.loading_window.hide()
512
- show_message(
513
- fontSize=12,
514
- icon=QtWidgets.QMessageBox.Icon.Critical,
515
- title="Seed Error",
516
- message=seed + " : No such seed exists in the repeats section of any organism selected."
517
- )
518
- return
519
-
520
- else:
521
-
522
- # increase row count
523
- self.table2.setRowCount(index + 1)
524
-
525
- # push seed to table
526
- table_seed = QtWidgets.QTableWidgetItem()
527
- table_seed.setData(QtCore.Qt.EditRole, seed)
528
- table_seed.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
529
- self.table2.setItem(index, 0, table_seed)
530
-
531
- self.counts.append(total_count)
532
-
533
- if len(threes) < len(fives):
534
- for i in range(len(fives) - len(threes)):
535
- threes.append('')
536
- elif len(fives) < len(threes):
537
- for i in range(len(threes) - len(fives)):
538
- fives.append('')
539
-
540
- majority_index = 0
541
- three_prime, five_prime, both_prime = False, False, False
542
- if threes[0] == '':
543
- majority = max(set(fives), key=fives.count)
544
- majority_index = fives.index(majority)
545
- five_prime = True
546
- elif fives[0] == '':
547
- majority = max(set(threes), key=threes.count)
548
- majority_index = threes.index(majority)
549
- three_prime = True
550
- else:
551
- # account for both 3 and 5 present
552
- threes_and_fives = []
553
- for i in range(len(threes)):
554
- threes_and_fives.append(threes[i] + fives[i])
555
- majority = max(set(threes_and_fives), key=threes_and_fives.count)
556
- majority_index = threes_and_fives.index(majority)
557
- both_prime = True
558
-
559
- # push percent coverage
560
- perc_cov = QtWidgets.QTableWidgetItem()
561
- coverage = (org_count / len(self.db_files)) * 100
562
- coverage = float("%.2f" % coverage)
563
- perc_cov.setData(QtCore.Qt.EditRole, coverage)
564
- perc_cov.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
565
- self.table2.setItem(index, 1, perc_cov)
566
-
567
- # push total count
568
- table_count = QtWidgets.QTableWidgetItem()
569
- table_count.setData(QtCore.Qt.EditRole, total_count)
570
- table_count.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
571
- self.table2.setItem(index, 2, table_count)
572
-
573
- # push avg repeat
574
- avg_rep = QtWidgets.QTableWidgetItem()
575
- avg_rep_per_scaff = total_count / org_count
576
- avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
577
- avg_rep.setData(QtCore.Qt.EditRole, avg_rep_per_scaff)
578
- avg_rep.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
579
- self.table2.setItem(index, 3, avg_rep)
580
-
581
- # push seq
582
- seq = QtWidgets.QTableWidgetItem()
583
- seq.setData(QtCore.Qt.EditRole, fives[majority_index] + seed + threes[majority_index])
584
- seq.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
585
- self.table2.setItem(index, 4, seq)
586
-
587
- # push percent consensus
588
- perc_cons = QtWidgets.QTableWidgetItem()
589
- percent_consensus = 0
590
- if five_prime == True:
591
- percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
592
- elif three_prime == True:
593
- percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
594
- elif both_prime:
595
- percent_consensus = (threes_and_fives.count(threes_and_fives[majority_index]) / len(threes_and_fives)) * 100
596
- percent_consensus = float("%.2f" % percent_consensus)
597
- perc_cons.setData(QtCore.Qt.EditRole, percent_consensus)
598
- perc_cons.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
599
- self.table2.setItem(index, 5, perc_cons)
600
-
601
- # push score
602
- score = QtWidgets.QTableWidgetItem()
603
- score.setData(QtCore.Qt.EditRole, scores[majority_index])
604
- score.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
605
- self.table2.setItem(index, 6, score)
606
-
607
- # push PAM
608
- pam = QtWidgets.QTableWidgetItem()
609
- pam.setData(QtCore.Qt.EditRole, pams[majority_index])
610
- pam.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
611
- self.table2.setItem(index, 7, pam)
612
-
613
- # push strand
614
- strand_val = ""
615
- if int(locs[majority_index]) < 0:
616
- strand_val = "-"
617
- else:
618
- strand_val = "+"
619
-
620
- strand = QtWidgets.QTableWidgetItem()
621
- strand.setData(QtCore.Qt.EditRole, strand_val)
622
- strand.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
623
- self.table2.setItem(index, 8, strand)
624
-
625
- index += 1
626
- running_val += increase_val
627
- self.loading_window.loading_bar.setValue(int(running_val))
628
-
629
- self.table2.resizeColumnsToContents()
630
- self.loading_window.hide()
631
- QtCore.QCoreApplication.processEvents()
632
- except Exception as e:
633
- show_error("Error in custom_seed_search() in population analysis.", e)
634
-
635
- #db_files is an array of database files for the organisms that will be looked at for shared seeds
636
- def get_shared_seeds(self, db_files, limit=False):
637
- try:
638
- aliases = []
639
-
640
- #get db attachment aliases
641
- for i in range(1, len(db_files) + 1):
642
- aliases.append("main" + str(i))
643
-
644
- #memory connections for inner join on db files to hold what seeds are shared
645
- new_conn = sqlite3.connect(GlobalSettings.appdir + "temp_join.db")
646
- new_c = new_conn.cursor()
647
- new_c.execute("PRAGMA synchronous = OFF;")
648
- new_c.execute("PRAGMA journal_mode = OFF;")
649
- new_c.execute("PRAGMA locking_mode = EXCLUSIVE;")
650
- new_c.execute("DROP TABLE IF EXISTS repeats;")
651
- new_c.execute("VACUUM;")
652
- new_c.execute("DROP TABLE IF EXISTS join_results;")
653
- new_c.execute("CREATE table join_results (seed TEXT PRIMARY KEY);")
654
-
655
- #attach each db file with an alias
656
- for i in range(len(db_files)):
657
- new_c.execute("ATTACH DATABASE '" + db_files[i] + "' AS " + aliases[i] + ";")
658
-
659
- # start transaction
660
- new_c.execute("BEGIN TRANSACTION;")
661
-
662
- sql_inner_join = "INSERT into main.join_results select main1.repeats.seed from main1.repeats "
663
-
664
- for i in range(len(aliases[:-1])):
665
- sql_inner_join += "inner join " + aliases[i + 1] + ".repeats on "
666
- sql_inner_join += aliases[i] + ".repeats.seed = " + aliases[i + 1] + ".repeats.seed "
667
-
668
- #execute inner join
669
- new_c.execute(sql_inner_join)
670
-
671
- #get shared data
672
- if limit == False:
673
- shared_seeds = new_c.execute("select count(*) from join_results").fetchall()
674
- return shared_seeds
675
- else:
676
- shared_seeds = new_c.execute("select * from join_results limit 0,1000").fetchall()
677
-
678
- #end transaction
679
- new_c.execute("END TRANSACTION;")
680
-
681
- #close memory db
682
- new_c.close()
683
- new_conn.close()
684
-
685
- #parse shared seeds into self.seeds
686
- seeds = []
687
- for tup in shared_seeds:
688
- seeds.append(tup[0])
689
-
690
- return seeds
691
-
692
- except Exception as e:
693
- show_error("Error in get_shared_seeds() in population analysis.", e)
694
-
695
- #get the names of organism in current directory
696
- def get_org_names(self):
697
- try:
698
- self.org_names = {}
699
- for file in self.cspr_files:
700
- with open(file, "r") as f:
701
- line = f.readline()
702
- buf = str(line)
703
- buf = buf.strip()
704
- org_name = buf.replace("GENOME: ", "")
705
-
706
- line = f.readline()
707
- buf = str(line)
708
- kstats = buf.replace("KARYSTATS: ", "")
709
- kstats = kstats.split(",")
710
- self.org_names[org_name] = len(kstats) - 1
711
- except Exception as e:
712
- show_error("Error in get_org_names() in population analysis.", e)
713
-
714
- #plot the heatmap graph
715
- def plot_3D_graph(self):
716
- try:
717
- for i in reversed(range(self.colormap_layout.count())): ### Clear out old widges in layout
718
- self.colormap_layout.itemAt(i).widget().setParent(None)
719
-
720
- self.colormap_canvas = MplCanvas(self, width=5, height=3, dpi=self.dpi) ###Initialize new Canvas
721
- self.colormap_layout.addWidget(self.colormap_canvas) ### Add canvas to colormap layout
722
- self.colormap_figure.setLayout(self.colormap_layout) ### Add colormap layout to colormap plot widget
723
-
724
- # self.colormap_canvas.axes.clear()
725
- # try:
726
- # self.colormap_canvas.cbar.remove()
727
- # except:
728
- # None
729
- # self.colormap_canvas.figure.set_visible(True)
730
-
731
- rows, cols = (self.total_org_number, self.total_org_number)
732
- arr = [[0 for i in range(cols)] for j in range(rows)]
733
-
734
- self.names = list(self.org_names.keys())
735
-
736
- for pair in list(itertools.combinations(self.db_files, 2)):
737
- shared_seeds = list(self.get_shared_seeds(list(pair), False)[0])[0]
738
-
739
- arr[self.db_files.index(pair[0])][self.db_files.index(pair[1])] += int(shared_seeds)
740
- arr[self.db_files.index(pair[1])][self.db_files.index(pair[0])] += int(shared_seeds)
741
-
742
- for i in range(len(arr)):
743
- conn = sqlite3.connect(self.db_files[i])
744
- c = conn.cursor()
745
- arr[i][i] = int(list(c.execute("select count(*) from repeats;").fetchall()[0])[0])
746
- c.close()
747
- conn.close()
748
-
749
- labels = copy.deepcopy(arr)
750
-
751
- for i in range(len(arr)):
752
- arr[i][i] = 0
753
-
754
- ax = self.colormap_canvas.axes
755
- im = self.colormap_canvas.axes.imshow(arr, cmap='summer')
756
- self.colormap_canvas.cbar = self.colormap_canvas.axes.figure.colorbar(im, ax=self.colormap_canvas.axes)
757
- self.colormap_canvas.cbar.ax.set_ylabel("", rotation=-90, va="bottom",fontsize=8)
758
- cursor = mplcursors.cursor(im, hover=True)
759
- @cursor.connect("add")
760
- def on_add(sel):
761
- sel.annotation.arrow_patch.set(arrowstyle="simple", fc="white", alpha=.5)
762
- sel.annotation.set_bbox(None)
763
- i,j = sel.target.index
764
- sel.annotation.set_text(labels[i][j])
765
-
766
- ax.set_xticks(np.arange(len(arr)))
767
- ax.set_yticks(np.arange(len(arr)))
768
-
769
- def plotCellGrid(data, ax=None, **kwargs):
770
- for x in range(data[0]):
771
- for y in range(data[1]):
772
- rect = patches.Rectangle((x - .5, y - .5), 1, 1, fill=False, **kwargs)
773
- ax.add_patch(rect)
774
-
775
- #get labels based on org table rows
776
- ax.set_xticklabels(self.rows)
777
- ax.set_yticklabels(self.rows)
778
- ax.set_xlabel("Organism", fontsize = 10)
779
- ax.set_ylabel("Organism", fontsize = 10)
780
- ax.tick_params(axis='both', which='major', labelsize=8)
781
- plotCellGrid([len(self.rows), len(self.rows)], ax, color="black", linewidth=1)
782
-
783
- self.colormap_canvas.draw()
784
- except Exception as e:
785
- show_error("Error in plot_3D_graph() in population analysis.", e)
786
-
787
- #find the locations of selected seeds to load into the location table
788
- def find_locations(self):
789
- try:
790
- if len(self.table2.selectedItems()) == 0:
791
- show_message(
792
- fontSize=12,
793
- icon=QtWidgets.QMessageBox.Icon.Critical,
794
- title="Error",
795
- message="Please select at least 1 seed to find locations of."
796
- )
797
- return
798
-
799
- #get seeds from selected rows in table
800
- seeds = []
801
- for i in self.table2.selectionModel().selectedRows():
802
- item = self.table2.item(i.row(), 0)
803
- seeds.append(item.text())
804
-
805
- index = 0
806
- org_names = list(self.org_names.keys())
807
- #loop through each db table looking for the seed, if found, insert data in to locations table
808
- for db_file in self.db_files:
809
- conn = sqlite3.connect(db_file)
810
- c = conn.cursor()
811
- for seed in seeds:
812
- data = c.execute("SELECT chromosome, location, five, three FROM repeats WHERE seed = ? ", (seed,)).fetchone()
813
- #make sure seed was found in table, then parse and store in table
814
- if data != None:
815
- data = list(data)
816
- chroms = data[0].split(',')
817
- locs = data[1].split(',')
818
- fives = data[2].split(',')
819
- threes = data[3].split(',')
820
- if threes[0] == '':
821
- threes = []
822
- if fives[0] == '':
823
- fives = []
824
- i = 0
825
- for chrom in chroms:
826
- self.loc_finder_table.setRowCount(index + 1)
827
- seed_table = QtWidgets.QTableWidgetItem()
828
- sequence_table = QtWidgets.QTableWidgetItem()
829
- organism_table = QtWidgets.QTableWidgetItem()
830
- chromsome_table = QtWidgets.QTableWidgetItem()
831
- location_table = QtWidgets.QTableWidgetItem()
832
- seed_table.setData(QtCore.Qt.EditRole, seed)
833
- if len(fives) == 0 and len(threes) != 0:
834
- sequence_table.setData(QtCore.Qt.EditRole, seed + threes[i])
835
- elif len(threes) == 0 and len(fives) != 0:
836
- sequence_table.setData(QtCore.Qt.EditRole, fives[i] + seed)
837
- else:
838
- sequence_table.setData(QtCore.Qt.EditRole, fives[i] + seed + threes[i])
839
- organism_table.setData(QtCore.Qt.EditRole, org_names[self.db_files.index(db_file)])
840
- chromsome_table.setData(QtCore.Qt.EditRole, chrom)
841
- location_table.setData(QtCore.Qt.EditRole, abs(int(locs[i])))
842
- self.loc_finder_table.setItem(index, 0, seed_table)
843
- self.loc_finder_table.setItem(index, 1, sequence_table)
844
- self.loc_finder_table.setItem(index, 2, organism_table)
845
- self.loc_finder_table.setItem(index, 3, chromsome_table)
846
- self.loc_finder_table.setItem(index, 4, location_table)
847
- i += 1
848
- index += 1
849
-
850
- self.loc_finder_table.resizeColumnsToContents()
851
- except Exception as e:
852
- show_error("Error in find_locations() in population analysis.", e)
853
-
854
- # this function clears the loc_finder_table
855
- def clear_loc_table(self):
856
- try:
857
- self.loc_finder_table.clearContents()
858
- self.loc_finder_table.setRowCount(0)
859
- except Exception as e:
860
- show_error("Error in clear_loc_table() in population analysis.", e)
861
-
862
- # sorting function for table2 - shared seeds table: IE the table in top-right
863
- def table2_sorting(self, logicalIndex):
864
- try:
865
- self.switcher_table2[logicalIndex] *= -1
866
- if self.switcher_table2[logicalIndex] == -1:
867
- self.table2.sortItems(logicalIndex, QtCore.Qt.DescendingOrder)
868
- else:
869
- self.table2.sortItems(logicalIndex, QtCore.Qt.AscendingOrder)
870
- except Exception as e:
871
- show_error("Error in table2_sorting() in population analysis.", e)
872
-
873
- # sorting for location table: IE table in bottom right
874
- def loc_table_sorter(self, logicalIndex):
875
- try:
876
- self.switcher_loc_table[logicalIndex] *= -1
877
- if (self.switcher_loc_table[logicalIndex] == -1):
878
- self.loc_finder_table.sortItems(logicalIndex, QtCore.Qt.DescendingOrder)
879
- else:
880
- self.loc_finder_table.sortItems(logicalIndex, QtCore.Qt.AscendingOrder)
881
- except Exception as e:
882
- show_error("Error in loc_table_sorter() in population analysis.", e)
883
-
884
- #clears the table showcasing shared seeds
885
- def clear(self):
886
- try:
887
- self.table2.setRowCount(0)
888
- except Exception as e:
889
- show_error("Error in clear() in population analysis.", e)
890
-
891
- #return to main function
892
- def go_back(self):
893
- try:
894
- GlobalSettings.mainWindow.show()
895
- self.hide()
896
- except Exception as e:
897
- show_error("Error in go_back() in population analysis.", e)
898
-
899
- # this function calls the close window class. Allows the user to choose what files they want to keep/delete
900
- def closeEvent(self, event):
901
- try:
902
- GlobalSettings.mainWindow.closeFunction()
903
- event.accept()
904
- except Exception as e:
905
- show_error("Error in closeEvent() in population analysis.", e)
906
-
907
- #loading window UI class for when data is loading
908
- class loading_window(QtWidgets.QMainWindow):
909
- def __init__(self):
910
- try:
911
- super(loading_window, self).__init__()
912
- uic.loadUi(GlobalSettings.appdir + "ui/loading_data_form.ui", self)
913
- self.loading_bar.setValue(0)
914
- self.setWindowTitle("Loading Data")
915
- self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
916
- scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=450, custom_scale_height=125)
917
- except Exception as e:
918
- show_error("Error initializing loading_window class in population analysis.", e)
919
-
920
- #matplotlib canvas class for the heatmap graph
921
- class MplCanvas(FigureCanvasQTAgg):
922
- def __init__(self, parent=None, width=400, height=250, dpi=100):
923
- try:
924
- fig = Figure(dpi=dpi, tight_layout=True)
925
- self.axes = fig.add_subplot(111)
926
- self.axes.clear()
927
- super(MplCanvas, self).__init__(fig)
928
- except Exception as e:
929
- show_error("Error initializing MplCanvas class in population analysis.", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/main.py CHANGED
@@ -1,32 +1,59 @@
1
  import sys
2
  import os
 
3
  from PyQt6.QtWidgets import QApplication
4
  from PyQt6.QtCore import Qt
5
  from models.GlobalSettings import GlobalSettings
6
  from utils.ui import show_error
7
- from controllers.MainWindowController import MainWindowController
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  def main():
10
- app = QApplication(sys.argv)
11
- app.setOrganizationName("TrinhLab-UTK")
12
- app.setApplicationName("CASPER")
 
 
 
 
 
 
13
 
14
- if hasattr(Qt.ApplicationAttribute, 'AA_UseHighDpiPixmaps'):
15
- app.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
16
 
17
- app_dir_path = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
 
 
 
 
 
 
18
 
19
- global_settings = GlobalSettings(app_dir_path)
20
- global_settings.apply_theme()
21
 
22
- try:
23
- main_window_controller = MainWindowController(global_settings)
24
- global_settings.set_main_window(main_window_controller)
25
- main_window_controller.show()
26
 
27
- app.exec()
28
- except Exception as e:
29
- show_error(global_settings, "An error occurred during application initialization", e)
 
 
30
 
31
  if __name__ == '__main__':
32
  main()
 
1
  import sys
2
  import os
3
+ import platform
4
  from PyQt6.QtWidgets import QApplication
5
  from PyQt6.QtCore import Qt
6
  from models.GlobalSettings import GlobalSettings
7
  from utils.ui import show_error
8
+
9
+ def get_app_directory():
10
+ """Determine the application root directory based on whether we're frozen or not"""
11
+ if hasattr(sys, 'frozen'):
12
+ if platform.system() == 'Darwin': # macOS
13
+ # Get the path to the executable inside the .app bundle
14
+ bundle_dir = os.path.abspath(os.path.dirname(sys.executable))
15
+ # Navigate up to Contents directory and set Resources as app_dir
16
+ return os.path.join(os.path.dirname(os.path.dirname(bundle_dir)), 'Contents', 'Resources')
17
+ else:
18
+ # For other platforms when frozen
19
+ return os.path.dirname(sys.executable)
20
+ else:
21
+ # Development environment - go up one level from src directory
22
+ return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23
 
24
  def main():
25
+ RESTART_CODE = 1000 # Define restart code constant
26
+
27
+ while True:
28
+ app = QApplication(sys.argv)
29
+ app.setOrganizationName("TrinhLab-UTK")
30
+ app.setApplicationName("CASPER")
31
+
32
+ if hasattr(Qt.ApplicationAttribute, 'AA_UseHighDpiPixmaps'):
33
+ app.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
34
 
35
+ # Get the application directory
36
+ app_dir_path = get_app_directory()
37
 
38
+ try:
39
+ global_settings = GlobalSettings(app_dir_path)
40
+
41
+ from controllers.MainWindowController import MainWindowController
42
+ main_window_controller = MainWindowController(global_settings)
43
+ global_settings.set_main_window(main_window_controller)
44
+ main_window_controller.show()
45
 
46
+ exit_code = app.exec()
 
47
 
48
+ if exit_code != RESTART_CODE:
49
+ sys.exit(exit_code)
50
+ break
 
51
 
52
+ main_window_controller = None
53
+ global_settings = None
54
+ app = None
55
+ except Exception as e:
56
+ show_error(global_settings, "An error occurred during application initialization", e)
57
 
58
  if __name__ == '__main__':
59
  main()
src/models/AnnotationParser.py CHANGED
@@ -19,7 +19,14 @@ class AnnotationParser:
19
  self.index_file = None
20
 
21
  def set_annotation_file(self, file_path):
 
22
  try:
 
 
 
 
 
 
23
  if self.annotation_file_name != file_path:
24
  total_start = time.time()
25
 
@@ -49,10 +56,9 @@ class AnnotationParser:
49
  start_time = time.time()
50
  self.logger.debug("Creating gene index file...")
51
 
52
- # Initialize index structure
53
  index_data = {
54
- 'locus_tags': {}, # Only store by locus_tag
55
- 'sequences': {} # Keep sequences for quick access
56
  }
57
 
58
  # Process records
@@ -63,44 +69,49 @@ class AnnotationParser:
63
  record_count += 1
64
  record_start = time.time()
65
 
66
- # Store sequence information first
67
- index_data['sequences'][record.id] = str(record.seq)
68
-
69
  # Process features
70
  for feature in record.features:
71
  if feature.type in ['CDS', 'gene']:
72
  feature_count += 1
73
- feature_info = self._get_feature_info(feature)
74
- locus_tag = feature_info['feature_id']
75
 
76
- # Only create feature entry if we have a valid locus_tag
 
 
 
 
 
 
 
77
  if locus_tag and locus_tag.lower() != "n/a":
 
 
 
 
 
 
78
  feature_entry = {
79
- 'record_id': record.id,
80
  'feature_type': feature.type,
81
  'chromosome': record.id,
82
- 'location': self._get_feature_location(feature),
83
- 'strand': '+' if feature.location.strand == 1 else '-',
84
- 'locus_tag': locus_tag,
85
- 'gene_name': feature_info['feature_name'],
86
- 'description': feature_info['feature_description'],
87
- 'qualifiers': {k: v[0] if isinstance(v, list) else v
88
- for k, v in feature.qualifiers.items()}
89
  }
90
 
91
- # Index only by locus_tag (lowercase for case-insensitive lookup)
92
- index_data['locus_tags'][locus_tag.lower()] = feature_entry
93
-
94
- record_time = time.time() - record_start
95
- if record_count % 100 == 0:
96
- self.logger.debug(f"Processed {record_count} records, {feature_count} features. Last record time: {record_time:.2f}s")
97
 
98
- # Save index to file
99
  save_start = time.time()
100
  with open(self.index_file, 'wb') as f:
101
- pickle.dump(index_data, f)
102
  save_time = time.time() - save_start
103
-
104
  total_time = time.time() - start_time
105
 
106
  self._index = index_data
@@ -127,6 +138,7 @@ class AnnotationParser:
127
  with open(self.index_file, 'rb') as f:
128
  self._index = pickle.load(f)
129
  load_time = time.time() - start_time
 
130
  self.logger.debug(f"Index file loaded successfully in {load_time:.2f} seconds")
131
  return True
132
 
@@ -137,47 +149,54 @@ class AnnotationParser:
137
  def genbank_search(self, queries):
138
  """Search using the index file for better performance"""
139
  try:
140
- if not self.annotation_file_name:
141
- raise ValueError("Annotation file not set")
 
142
 
143
  self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
144
  results_list = []
145
 
146
  # Convert queries to lowercase set for faster lookup
147
  queries = {q.lower() for q in queries}
148
- print(f"Search queries: {queries}")
149
 
150
  # Search through index
151
- if hasattr(self, '_index'):
152
- # Search through all features
153
- for feature_key, feature_entry in self._index['locus_tags'].items():
 
 
 
 
 
 
 
154
  # Check gene name, locus tag, and description
155
  searchable_text = ' '.join([
156
- feature_entry['gene_name'].lower(),
157
- feature_entry['locus_tag'].lower(),
158
- feature_entry['description'].lower(),
159
- # Also search through qualifiers
160
- *[str(v).lower() for v in feature_entry['qualifiers'].values()]
161
  ])
162
 
163
  # Check if any query matches
164
  if any(query in searchable_text for query in queries):
165
  info = {
166
- 'feature_id': feature_entry['locus_tag'],
167
- 'feature_name': feature_entry['gene_name'],
168
- 'feature_location': feature_entry['location'],
169
- 'feature_description': feature_entry['description']
170
  }
171
- results_list.append((feature_entry['record_id'], info))
172
 
173
  return results_list
174
 
175
  except Exception as e:
176
  self.logger.error(f"Error in genbank_search: {str(e)}")
 
177
  raise
178
 
179
  def get_gene_data(self, gene_identifier):
180
- """Get gene data using the index for faster retrieval"""
181
  try:
182
  if not gene_identifier:
183
  return None
@@ -189,27 +208,81 @@ class AnnotationParser:
189
  # Try exact match first
190
  if gene_identifier in self._index['locus_tags']:
191
  gene_info = self._index['locus_tags'][gene_identifier]
192
- record_id = gene_info['record_id']
193
- return {
194
- 'sequence': self._index['sequences'][record_id],
195
- 'info': gene_info
 
 
 
 
 
 
 
 
 
 
 
196
  }
197
 
 
 
 
 
 
198
  # Try case-insensitive match
199
  for key, value in self._index['locus_tags'].items():
200
  if str(key).lower() == gene_identifier:
201
- record_id = value['record_id']
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  return {
203
- 'sequence': self._index['sequences'][record_id],
204
- 'info': value
205
  }
206
 
207
  return None
208
-
209
  except Exception as e:
210
  self.logger.error(f"Error in get_gene_data: {str(e)}")
211
  return None
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  @lru_cache(maxsize=1)
214
  def _get_records(self):
215
  """Cache and return all records from the annotation file"""
 
19
  self.index_file = None
20
 
21
  def set_annotation_file(self, file_path):
22
+ """Set the annotation file and initialize/load index"""
23
  try:
24
+ # Don't process if file_path is a directory or empty
25
+ if not file_path or os.path.isdir(file_path):
26
+ self.logger.debug(f"Invalid annotation file path: {file_path}")
27
+ self._index = {'locus_tags': {}} # Initialize empty index
28
+ return
29
+
30
  if self.annotation_file_name != file_path:
31
  total_start = time.time()
32
 
 
56
  start_time = time.time()
57
  self.logger.debug("Creating gene index file...")
58
 
59
+ # Initialize optimized index structure - no sequences stored
60
  index_data = {
61
+ 'locus_tags': {}, # Only store essential data
 
62
  }
63
 
64
  # Process records
 
69
  record_count += 1
70
  record_start = time.time()
71
 
 
 
 
72
  # Process features
73
  for feature in record.features:
74
  if feature.type in ['CDS', 'gene']:
75
  feature_count += 1
 
 
76
 
77
+ # Get essential feature info
78
+ locus_tag = None
79
+ if 'locus_tag' in feature.qualifiers:
80
+ locus_tag = feature.qualifiers['locus_tag'][0]
81
+ elif 'gene' in feature.qualifiers:
82
+ locus_tag = feature.qualifiers['gene'][0]
83
+
84
+ # Only process features with valid locus tags
85
  if locus_tag and locus_tag.lower() != "n/a":
86
+ # Get location info
87
+ start = int(feature.location.start)
88
+ end = int(feature.location.end)
89
+ strand = '+' if feature.location.strand == 1 else '-'
90
+
91
+ # Store feature info with full names
92
  feature_entry = {
 
93
  'feature_type': feature.type,
94
  'chromosome': record.id,
95
+ 'location': f"{start}:{end}({strand})",
96
+ 'gene_name': feature.qualifiers.get('gene', ['N/A'])[0],
97
+ 'description': feature.qualifiers.get('product',
98
+ feature.qualifiers.get('note', ['N/A']))[0],
99
+ 'start': start,
100
+ 'end': end
 
101
  }
102
 
103
+ # Store in index
104
+ index_data['locus_tags'][locus_tag] = feature_entry
105
+
106
+ record_time = time.time() - record_start
107
+ if record_count % 100 == 0:
108
+ self.logger.debug(f"Processed {record_count} records, {feature_count} features. Last record time: {record_time:.2f}s")
109
 
110
+ # Save compressed index to file
111
  save_start = time.time()
112
  with open(self.index_file, 'wb') as f:
113
+ pickle.dump(index_data, f, protocol=pickle.HIGHEST_PROTOCOL)
114
  save_time = time.time() - save_start
 
115
  total_time = time.time() - start_time
116
 
117
  self._index = index_data
 
138
  with open(self.index_file, 'rb') as f:
139
  self._index = pickle.load(f)
140
  load_time = time.time() - start_time
141
+ print(f"Index file: {self._index}")
142
  self.logger.debug(f"Index file loaded successfully in {load_time:.2f} seconds")
143
  return True
144
 
 
149
  def genbank_search(self, queries):
150
  """Search using the index file for better performance"""
151
  try:
152
+ if not self.annotation_file_name or os.path.isdir(self.annotation_file_name):
153
+ self.logger.warning("No valid annotation file set")
154
+ return []
155
 
156
  self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
157
  results_list = []
158
 
159
  # Convert queries to lowercase set for faster lookup
160
  queries = {q.lower() for q in queries}
161
+ self.logger.debug(f"Search queries: {queries}")
162
 
163
  # Search through index
164
+ if hasattr(self, '_index') and 'locus_tags' in self._index:
165
+ # Search through features, filtering for CDS and gene types only
166
+ for locus_tag, feature_entry in self._index['locus_tags'].items():
167
+ # Safely get feature type with default value
168
+ feature_type = feature_entry.get('feature_type', '')
169
+
170
+ # Only process CDS and gene features
171
+ if feature_type not in ['CDS', 'gene']:
172
+ continue
173
+
174
  # Check gene name, locus tag, and description
175
  searchable_text = ' '.join([
176
+ feature_entry.get('gene_name', '').lower(),
177
+ locus_tag.lower(),
178
+ feature_entry.get('description', '').lower()
 
 
179
  ])
180
 
181
  # Check if any query matches
182
  if any(query in searchable_text for query in queries):
183
  info = {
184
+ 'feature_id': locus_tag,
185
+ 'feature_name': feature_entry.get('gene_name', 'N/A'),
186
+ 'feature_location': feature_entry.get('location', 'N/A'),
187
+ 'feature_description': feature_entry.get('description', 'N/A')
188
  }
189
+ results_list.append((feature_entry.get('chromosome', ''), info))
190
 
191
  return results_list
192
 
193
  except Exception as e:
194
  self.logger.error(f"Error in genbank_search: {str(e)}")
195
+ self.logger.error(f"Stack trace: {traceback.format_exc()}") # Add stack trace for better debugging
196
  raise
197
 
198
  def get_gene_data(self, gene_identifier):
199
+ """Get gene data using the optimized index and fetch sequence on demand"""
200
  try:
201
  if not gene_identifier:
202
  return None
 
208
  # Try exact match first
209
  if gene_identifier in self._index['locus_tags']:
210
  gene_info = self._index['locus_tags'][gene_identifier]
211
+
212
+ # Get sequence from file
213
+ sequence = self._get_sequence_for_gene(gene_info)
214
+ if sequence is None:
215
+ return None
216
+
217
+ # Use full names instead of shortened keys
218
+ expanded_info = {
219
+ 'feature_type': gene_info['feature_type'],
220
+ 'chromosome': gene_info['chromosome'],
221
+ 'location': gene_info['location'],
222
+ 'gene_name': gene_info['gene_name'],
223
+ 'description': gene_info['description'],
224
+ 'start': gene_info['start'],
225
+ 'end': gene_info['end']
226
  }
227
 
228
+ return {
229
+ 'sequence': sequence,
230
+ 'info': expanded_info
231
+ }
232
+
233
  # Try case-insensitive match
234
  for key, value in self._index['locus_tags'].items():
235
  if str(key).lower() == gene_identifier:
236
+ sequence = self._get_sequence_for_gene(value)
237
+ if sequence is None:
238
+ return None
239
+
240
+ expanded_info = {
241
+ 'feature_type': value['feature_type'],
242
+ 'chromosome': value['chromosome'],
243
+ 'location': value['location'],
244
+ 'gene_name': value['gene_name'],
245
+ 'description': value['description'],
246
+ 'start': value['start'],
247
+ 'end': value['end']
248
+ }
249
+
250
  return {
251
+ 'sequence': sequence,
252
+ 'info': expanded_info
253
  }
254
 
255
  return None
256
+
257
  except Exception as e:
258
  self.logger.error(f"Error in get_gene_data: {str(e)}")
259
  return None
260
 
261
+ def _get_sequence_for_gene(self, gene_info):
262
+ """Get sequence for a gene from the GenBank file"""
263
+ try:
264
+ self.logger.debug(f"Getting sequence for gene info: {gene_info} in _get_sequence_for_gene")
265
+ # Parse the GenBank file and find the right record
266
+ for record in SeqIO.parse(self.annotation_file_name, "genbank"):
267
+ if record.id == gene_info['chromosome']: # Use full chromosome name
268
+ sequence = str(record.seq)
269
+
270
+ # Get sequence with padding
271
+ padding = 30
272
+ start = max(0, gene_info['start'] - padding)
273
+ end = min(len(sequence), gene_info['end'] + padding)
274
+ padded_sequence = sequence[start:end]
275
+
276
+ self.logger.debug(f"Padded sequence: {padded_sequence}")
277
+
278
+ return padded_sequence
279
+
280
+ return None
281
+
282
+ except Exception as e:
283
+ self.logger.error(f"Error getting sequence for gene: {str(e)}")
284
+ return None
285
+
286
  @lru_cache(maxsize=1)
287
  def _get_records(self):
288
  """Cache and return all records from the annotation file"""
src/models/CoTargetingModel.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class CoTargetingModel:
2
+ def __init__(self, global_settings):
3
+ self.settings = global_settings
4
+ self.logger = global_settings.get_logger()
5
+ self.endo_data = {}
6
+ self.get_endo_data()
7
+
8
+ def get_endo_data(self):
9
+ """Load endonuclease data from CASPERinfo file"""
10
+ try:
11
+ f = open(self.settings.get_casper_info_path())
12
+ while True:
13
+ line = f.readline()
14
+ if line.startswith('ENDONUCLEASES'):
15
+ while True:
16
+ line = f.readline()
17
+ if line[0] == "-":
18
+ break
19
+ line_tokened = line.split(";")
20
+ endo = line_tokened[0]
21
+ self.endo_data[endo] = ([line_tokened[2], line_tokened[3], line_tokened[4]], line_tokened[5])
22
+ break
23
+ f.close()
24
+ except Exception as e:
25
+ self.logger.error(f"Error loading endonuclease data: {str(e)}")
26
+ raise
27
+
28
+ def validate_endonucleases(self, endo_list):
29
+ """Validate that selected endonucleases are compatible"""
30
+ try:
31
+ for endo1 in endo_list:
32
+ for endo2 in endo_list:
33
+ if endo1 == endo2:
34
+ continue
35
+ # Check gRNA length compatibility
36
+ endo1_len = sum([int(x) for x in self.endo_data[endo1][0]])
37
+ endo2_len = sum([int(x) for x in self.endo_data[endo2][0]])
38
+
39
+ # Check directionality compatibility
40
+ if (endo1_len != endo2_len or
41
+ self.endo_data[endo1][1] != self.endo_data[endo2][1]):
42
+ return False
43
+ return True
44
+ except Exception as e:
45
+ self.logger.error(f"Error validating endonucleases: {str(e)}")
46
+ return False
47
+
48
+ def format_endo_combination(self, endo_list):
49
+ """Format selected endonucleases into combined string"""
50
+ return "|".join(endo_list)
src/models/ConfigManager.py CHANGED
@@ -159,25 +159,24 @@ class ConfigManager(QObject):
159
  fields = line.strip().split(';')
160
  print(fields)
161
  if len(fields) == 10:
162
- endonuclease_name = fields[0]
163
- endonuclease_abbreviation = fields[1]
164
- # pam_sequence = self._get_primary_pam(fields[1])
165
- endonuclease_CRISPR_type = fields[2]
166
- pam_sequence = fields[3]
167
- five_prime_length, seed_length, three_prime_length, direction = fields[4:8]
168
  endonuclease_on_target_matrix = fields[8]
169
  endonuclease_off_target_matrix = fields[9]
170
 
171
  endonuclease_key = f"{endonuclease_abbreviation} - PAM: {pam_sequence}"
172
  endonuclease_value = {
173
- 'endonuclease_organism': endonuclease_name,
174
  'endonuclease_abbreviation': endonuclease_abbreviation,
175
- 'endonuclease_CRISPR_type': endonuclease_CRISPR_type,
176
  'endonuclease_pam_sequence': pam_sequence,
177
  'endonuclease_five_prime_length': five_prime_length,
178
  'endonuclease_seed_length': seed_length,
179
  'endonuclease_three_prime_length': three_prime_length,
180
  'endonuclease_direction': direction,
 
 
181
  'endonuclease_on_target_scoring': endonuclease_on_target_matrix,
182
  'endonuclease_off_target_scoring': endonuclease_off_target_matrix
183
  }
 
159
  fields = line.strip().split(';')
160
  print(fields)
161
  if len(fields) == 10:
162
+ endonuclease_abbreviation = fields[0]
163
+ pam_sequence = fields[1]
164
+ five_prime_length, seed_length, three_prime_length, direction = fields[2:6]
165
+ endonuclease_name = fields[6]
166
+ endonuclease_CRISPR_type = fields[7]
 
167
  endonuclease_on_target_matrix = fields[8]
168
  endonuclease_off_target_matrix = fields[9]
169
 
170
  endonuclease_key = f"{endonuclease_abbreviation} - PAM: {pam_sequence}"
171
  endonuclease_value = {
 
172
  'endonuclease_abbreviation': endonuclease_abbreviation,
 
173
  'endonuclease_pam_sequence': pam_sequence,
174
  'endonuclease_five_prime_length': five_prime_length,
175
  'endonuclease_seed_length': seed_length,
176
  'endonuclease_three_prime_length': three_prime_length,
177
  'endonuclease_direction': direction,
178
+ 'endonuclease_organism': endonuclease_name,
179
+ 'endonuclease_CRISPR_type': endonuclease_CRISPR_type,
180
  'endonuclease_on_target_scoring': endonuclease_on_target_matrix,
181
  'endonuclease_off_target_scoring': endonuclease_off_target_matrix
182
  }
src/models/DatabaseManager.py CHANGED
@@ -3,19 +3,33 @@ from PyQt6.QtCore import QObject, pyqtSignal, QFileSystemWatcher
3
  import sqlite3
4
  from collections import Counter
5
  import statistics
 
 
 
 
 
 
 
 
 
6
 
7
  class DatabaseManager(QObject):
8
- db_state_updated = pyqtSignal(bool, str, list) # Combined signal
 
 
9
 
10
  def __init__(self, logger, config_manager):
11
  super().__init__()
12
  self.logger = logger
13
  self.config_manager = config_manager
14
  self.db_path = None
15
- self.file_watcher = QFileSystemWatcher() # Initialize file_watcher here
16
  self.file_watcher.directoryChanged.connect(self._on_directory_changed)
17
  self.load_database_path()
18
  self._update_watched_directory()
 
 
 
19
 
20
  def load_database_path(self):
21
  """Load the database path from .env file or set default if empty."""
@@ -29,17 +43,20 @@ class DatabaseManager(QObject):
29
  return self.db_path
30
 
31
  def validate_db_path(self, path):
32
- """Validate that the given path exists and contains CSPR files."""
33
  self.logger.debug(f"Validating DB path: {path}")
 
34
  if not os.path.isdir(path):
35
  self.logger.debug(f"Path is not a directory: {path}")
36
  return False, "The selected path is not a directory."
37
- has_cspr_files = any(file.endswith(".cspr") for file in os.listdir(path))
38
- if not has_cspr_files:
 
39
  self.logger.debug(f"Path {path} does not contain CSPR files")
40
  return False, "The selected directory does not contain any CSPR files."
41
- self.logger.debug(f"Path {path} is valid and contains CSPR files")
42
- return True, "Valid database path selected."
 
43
 
44
  def save_db_path(self, path):
45
  """Set and save the database path."""
@@ -54,7 +71,7 @@ class DatabaseManager(QObject):
54
  is_valid, message = self.validate_db_path(path)
55
  if not is_valid:
56
  self.logger.warning(f"Invalid database path: {path}")
57
- self.db_state_updated.emit(False, message, [])
58
  self.db_path = path
59
  self.config_manager.set_env_value('CSPR_DB', path)
60
  self._update_watched_directory()
@@ -66,13 +83,13 @@ class DatabaseManager(QObject):
66
  try:
67
  self.config_manager.set_env_value('CSPR_DB', path)
68
  self.logger.info(f"Database path set and saved: {path}")
69
- self.db_state_updated.emit(True, "Database path saved successfully.", [])
70
  self._update_watched_directory()
71
  return True, "Database path saved successfully."
72
  except Exception as e:
73
  error_message = f"Error saving database path: {str(e)}"
74
  self.logger.error(error_message)
75
- self.db_state_updated.emit(False, error_message, [])
76
  return False, error_message
77
 
78
  def get_db_path(self):
@@ -108,61 +125,112 @@ class DatabaseManager(QObject):
108
  return adjusted_path
109
 
110
  def _update_watched_directory(self):
111
- """Update the directory being watched by QFileSystemWatcher."""
112
- if self.file_watcher.directories():
113
- self.file_watcher.removePaths(self.file_watcher.directories())
114
  if self.db_path and os.path.isdir(self.db_path):
115
  self.file_watcher.addPath(self.db_path)
116
- self.logger.debug(f"Now watching directory: {self.db_path}")
 
 
 
 
 
 
117
 
118
- def _on_directory_changed(self, path):
119
- """Handle changes in the watched directory."""
120
- self.logger.debug(f"Detected change in directory: {path}")
 
121
 
122
- # Get current state
123
- is_valid, message = self.validate_db_path(path)
124
 
125
- # Get list of files
126
- cspr_files = self._get_cspr_files()
127
- gbff_files = self._get_gbff_files() # Add method to get GBFF files
128
 
129
- # Emit the signal with updated state
130
- self.db_state_updated.emit(is_valid, message, cspr_files)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- # Log the change
133
- self.logger.info(f"Database state updated - Valid: {is_valid}, Files: {len(cspr_files)} CSPR, {len(gbff_files)} GBFF")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  def _get_cspr_files(self):
136
- """Get a list of CSPR files in the current database directory."""
137
  if not self.db_path or not os.path.isdir(self.db_path):
138
  return []
139
- return [f for f in os.listdir(self.db_path) if f.endswith('.cspr')]
 
140
 
141
  def _get_gbff_files(self):
142
- """Get a list of GBFF files in the database directory."""
143
  if not self.db_path or not os.path.isdir(self.db_path):
144
  return []
145
  gbff_path = os.path.join(self.db_path, 'GBFF')
146
  if not os.path.exists(gbff_path):
147
  return []
148
- return [f for f in os.listdir(gbff_path) if f.endswith('.gbff')]
 
149
 
150
  def check_db_state(self):
151
- """Check the current state of the database and emit signals if changed."""
152
  self.logger.debug("Checking database state")
153
  if not self.db_path:
154
  self.load_database_path()
155
 
 
156
  is_valid, message = self.validate_db_path(self.db_path)
157
- self.logger.debug(f"Database state: valid={is_valid}, message={message}")
158
 
159
- cspr_files = self._get_cspr_files()
160
- gbff_files = self._get_gbff_files()
161
-
162
- message = f"Database is valid. Contains {len(cspr_files)} CSPR files and {len(gbff_files)} GBFF files."
163
 
164
- self.db_state_updated.emit(is_valid, message, cspr_files)
165
- self.logger.info(f"Database state checked - Valid: {is_valid}, Files: {len(cspr_files)} CSPR, {len(gbff_files)} GBFF")
 
 
 
 
166
 
167
  def get_organisms_and_endos(self):
168
  """Get mapping of organisms to their endonucleases and files"""
@@ -180,8 +248,8 @@ class DatabaseManager(QObject):
180
  for file in cspr_files:
181
  try:
182
  # Parse filename
183
- newname = file[0:-5] # Remove .cspr - changed from -4 to -5
184
- endo = newname[newname.rfind("_") + 1:] # Get endonuclease name
185
 
186
  # Read organism name from first line of CSPR file
187
  file_path = os.path.join(self.db_path, file)
@@ -191,17 +259,17 @@ class DatabaseManager(QObject):
191
 
192
  # Store file mappings
193
  if species in organisms_to_files:
194
- organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
195
  else:
196
  organisms_to_files[species] = {}
197
- organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
198
 
199
  # Store endonuclease mappings
200
  if species in organisms_to_endos:
201
- if endo not in organisms_to_endos[species]:
202
- organisms_to_endos[species].append(endo)
203
  else:
204
- organisms_to_endos[species] = [endo]
205
 
206
  except Exception as e:
207
  self.logger.error(f"Error processing file {file}: {str(e)}")
 
3
  import sqlite3
4
  from collections import Counter
5
  import statistics
6
+ from enum import Enum
7
+ from typing import Set, Dict, List, Tuple
8
+
9
+ class FileChangeType(Enum):
10
+ CSPR_ADDED = "cspr_added"
11
+ CSPR_REMOVED = "cspr_removed"
12
+ GBFF_ADDED = "gbff_added"
13
+ GBFF_REMOVED = "gbff_removed"
14
+ OTHER = "other"
15
 
16
  class DatabaseManager(QObject):
17
+ db_files_changed = pyqtSignal(dict) # Emits a dict of FileChangeType: List[str]
18
+ db_validation_changed = pyqtSignal(bool, str) # Emits validation state and message
19
+ db_state_changed = pyqtSignal(bool, str, dict) # Emits (is_valid, message, changes)
20
 
21
  def __init__(self, logger, config_manager):
22
  super().__init__()
23
  self.logger = logger
24
  self.config_manager = config_manager
25
  self.db_path = None
26
+ self.file_watcher = QFileSystemWatcher()
27
  self.file_watcher.directoryChanged.connect(self._on_directory_changed)
28
  self.load_database_path()
29
  self._update_watched_directory()
30
+
31
+ self._last_cspr_files: Set[str] = set(self._get_cspr_files())
32
+ self._last_gbff_files: Set[str] = set(self._get_gbff_files())
33
 
34
  def load_database_path(self):
35
  """Load the database path from .env file or set default if empty."""
 
43
  return self.db_path
44
 
45
  def validate_db_path(self, path):
46
+ """Validate that the given path exists and contains CSPR files"""
47
  self.logger.debug(f"Validating DB path: {path}")
48
+
49
  if not os.path.isdir(path):
50
  self.logger.debug(f"Path is not a directory: {path}")
51
  return False, "The selected path is not a directory."
52
+
53
+ cspr_files = self._get_cspr_files()
54
+ if not cspr_files:
55
  self.logger.debug(f"Path {path} does not contain CSPR files")
56
  return False, "The selected directory does not contain any CSPR files."
57
+
58
+ self.logger.debug(f"Path {path} is valid and contains {len(cspr_files)} CSPR files")
59
+ return True, f"Valid database path with {len(cspr_files)} CSPR files"
60
 
61
  def save_db_path(self, path):
62
  """Set and save the database path."""
 
71
  is_valid, message = self.validate_db_path(path)
72
  if not is_valid:
73
  self.logger.warning(f"Invalid database path: {path}")
74
+ self.db_validation_changed.emit(False, message)
75
  self.db_path = path
76
  self.config_manager.set_env_value('CSPR_DB', path)
77
  self._update_watched_directory()
 
83
  try:
84
  self.config_manager.set_env_value('CSPR_DB', path)
85
  self.logger.info(f"Database path set and saved: {path}")
86
+ self.db_validation_changed.emit(True, "Database path saved successfully.")
87
  self._update_watched_directory()
88
  return True, "Database path saved successfully."
89
  except Exception as e:
90
  error_message = f"Error saving database path: {str(e)}"
91
  self.logger.error(error_message)
92
+ self.db_validation_changed.emit(False, error_message)
93
  return False, error_message
94
 
95
  def get_db_path(self):
 
125
  return adjusted_path
126
 
127
  def _update_watched_directory(self):
128
+ self.file_watcher.removePaths(self.file_watcher.directories())
129
+
 
130
  if self.db_path and os.path.isdir(self.db_path):
131
  self.file_watcher.addPath(self.db_path)
132
+
133
+ # Also watch GBFF subdirectory if it exists
134
+ gbff_path = os.path.join(self.db_path, 'GBFF')
135
+ if os.path.isdir(gbff_path):
136
+ self.file_watcher.addPath(gbff_path)
137
+
138
+ self.logger.debug(f"Now watching directories: {self.file_watcher.directories()}")
139
 
140
+ def _detect_file_changes(self) -> Dict[FileChangeType, List[str]]:
141
+ """Detect what files have changed and categorize the changes"""
142
+ current_cspr_files = set(self._get_cspr_files())
143
+ current_gbff_files = set(self._get_gbff_files())
144
 
145
+ changes = {}
 
146
 
147
+ # Detect CSPR changes
148
+ cspr_added = current_cspr_files - self._last_cspr_files
149
+ cspr_removed = self._last_cspr_files - current_cspr_files
150
 
151
+ if cspr_added:
152
+ changes[FileChangeType.CSPR_ADDED] = list(cspr_added)
153
+ if cspr_removed:
154
+ changes[FileChangeType.CSPR_REMOVED] = list(cspr_removed)
155
+
156
+ # Detect GBFF changes
157
+ gbff_added = current_gbff_files - self._last_gbff_files
158
+ gbff_removed = self._last_gbff_files - current_gbff_files
159
+
160
+ if gbff_added:
161
+ changes[FileChangeType.GBFF_ADDED] = list(gbff_added)
162
+ if gbff_removed:
163
+ changes[FileChangeType.GBFF_REMOVED] = list(gbff_removed)
164
+
165
+ # Update last known state
166
+ self._last_cspr_files = current_cspr_files
167
+ self._last_gbff_files = current_gbff_files
168
 
169
+ return changes
170
+
171
+ def _on_directory_changed(self, path):
172
+ """Handle changes in the watched directory"""
173
+ try:
174
+ self.logger.debug(f"Detected change in directory: {path}")
175
+
176
+ # Detect specific changes
177
+ changes = self._detect_file_changes()
178
+
179
+ if changes: # Only emit if there are actual changes
180
+ self.logger.debug(f"Detected file changes: {changes}")
181
+
182
+ # Get validation state
183
+ is_valid, message = self.validate_db_path(path)
184
+
185
+ # Emit separate signals
186
+ self.db_validation_changed.emit(is_valid, message)
187
+ self.db_files_changed.emit(changes)
188
+
189
+ # Emit combined signal for components that want everything
190
+ self.db_state_changed.emit(is_valid, message, changes)
191
+
192
+ self.logger.info(f"Database state updated - Valid: {is_valid}, Changes: {changes}")
193
+ else:
194
+ self.logger.debug("No relevant file changes detected")
195
+
196
+ except Exception as e:
197
+ self.logger.error(f"Error handling directory change: {str(e)}")
198
 
199
  def _get_cspr_files(self):
200
+ """Get a list of CSPR files in the current database directory"""
201
  if not self.db_path or not os.path.isdir(self.db_path):
202
  return []
203
+ return [f for f in os.listdir(self.db_path)
204
+ if f.endswith('.cspr')]
205
 
206
  def _get_gbff_files(self):
207
+ """Get a list of GBFF files in the database directory"""
208
  if not self.db_path or not os.path.isdir(self.db_path):
209
  return []
210
  gbff_path = os.path.join(self.db_path, 'GBFF')
211
  if not os.path.exists(gbff_path):
212
  return []
213
+ return [f for f in os.listdir(gbff_path)
214
+ if f.endswith('.gbff')]
215
 
216
  def check_db_state(self):
217
+ """Check the current state of the database and emit signals if needed."""
218
  self.logger.debug("Checking database state")
219
  if not self.db_path:
220
  self.load_database_path()
221
 
222
+ # Get validation state
223
  is_valid, message = self.validate_db_path(self.db_path)
 
224
 
225
+ # Detect any changes since last check
226
+ changes = self._detect_file_changes()
 
 
227
 
228
+ # Emit signals
229
+ self.db_validation_changed.emit(is_valid, message)
230
+ if changes:
231
+ self.db_files_changed.emit(changes)
232
+
233
+ self.logger.info(f"Database state checked - Valid: {is_valid}, Changes: {changes}")
234
 
235
  def get_organisms_and_endos(self):
236
  """Get mapping of organisms to their endonucleases and files"""
 
248
  for file in cspr_files:
249
  try:
250
  # Parse filename
251
+ newname = file[0:-5]
252
+ endonuclease = newname[newname.rfind("_") + 1:]
253
 
254
  # Read organism name from first line of CSPR file
255
  file_path = os.path.join(self.db_path, file)
 
259
 
260
  # Store file mappings
261
  if species in organisms_to_files:
262
+ organisms_to_files[species][endonuclease] = [file, file.replace(".cspr", "_repeats.db")]
263
  else:
264
  organisms_to_files[species] = {}
265
+ organisms_to_files[species][endonuclease] = [file, file.replace(".cspr", "_repeats.db")]
266
 
267
  # Store endonuclease mappings
268
  if species in organisms_to_endos:
269
+ if endonuclease not in organisms_to_endos[species]:
270
+ organisms_to_endos[species].append(endonuclease)
271
  else:
272
+ organisms_to_endos[species] = [endonuclease]
273
 
274
  except Exception as e:
275
  self.logger.error(f"Error processing file {file}: {str(e)}")
src/models/ExportSelectedgRNAsModel.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Dict, List
3
+ from utils.sequence_utils import get_table_headers
4
+
5
+ class ExportSelectedgRNAsModel:
6
+ def __init__(self, global_settings):
7
+ self.global_settings = global_settings
8
+ self.logger = global_settings.get_logger()
9
+ self.data = {
10
+ 'selected_items': [],
11
+ 'window_type': "",
12
+ 'has_locus_tag': False,
13
+ 'has_gene_name': False
14
+ }
15
+
16
+ def set_export_data(self, items: List, window_type: str) -> None:
17
+ """Set the selected items and window type"""
18
+ try:
19
+ self.data['selected_items'] = items
20
+ self.data['window_type'] = window_type
21
+ self.logger.debug(f"Set export data: {len(items)} items, window type: {window_type}")
22
+ except Exception as e:
23
+ self.logger.error(f"Error setting export data: {str(e)}")
24
+ raise
25
+
26
+ def get_headers(self) -> List[str]:
27
+ """Get appropriate headers based on window type"""
28
+ try:
29
+ if self.data['window_type'] == "Multitargeting":
30
+ return self._get_multitargeting_headers()
31
+ elif self.data['window_type'] == "Population Analysis":
32
+ return self._get_population_analysis_headers()
33
+ else:
34
+ return self._get_view_targets_headers()
35
+ except Exception as e:
36
+ self.logger.error(f"Error getting headers: {str(e)}")
37
+ raise
38
+
39
+ def _get_multitargeting_headers(self) -> List[str]:
40
+ """Get headers for multitargeting window"""
41
+ try:
42
+ headers = [
43
+ "Seed", "Total Repeats", "Avg. Repeats/Scaffold",
44
+ "Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
45
+ ]
46
+
47
+ insertion_index = headers.index("Consensus Sequence")
48
+ headers.insert(insertion_index + 1, "Full Sequence")
49
+
50
+ self.logger.debug(f"Multitargeting headers: {headers}")
51
+ return headers
52
+
53
+ except Exception as e:
54
+ self.logger.error(f"Error getting multitargeting headers: {str(e)}")
55
+ raise
56
+
57
+ def _get_population_analysis_headers(self) -> List[str]:
58
+ """Get headers for population analysis window"""
59
+ try:
60
+ headers = [
61
+ "Seed", "% Coverage", "Total Repeats", "Avg. Repeats/Scaffold",
62
+ "Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
63
+ ]
64
+
65
+ insertion_index = headers.index("Consensus Sequence")
66
+ headers.insert(insertion_index + 1, "Full Sequence")
67
+
68
+ self.logger.debug(f"Population Analysis headers: {headers}")
69
+ return headers
70
+
71
+ except Exception as e:
72
+ self.logger.error(f"Error getting population analysis headers: {str(e)}")
73
+ raise
74
+
75
+ def _get_view_targets_headers(self) -> List[str]:
76
+ """Get headers for view targets window"""
77
+ try:
78
+ headers = [
79
+ "Location", "Endonuclease", "Sequence", "Strand",
80
+ "PAM", "Score", "Off-Target"
81
+ ]
82
+
83
+ sequence_index = headers.index("Sequence")
84
+ headers.insert(sequence_index + 1, "Full Sequence")
85
+
86
+ if self.data['selected_items']:
87
+ first_target = self.data['selected_items'][0]
88
+
89
+ if 'locus_tag' in first_target or 'feature_id' in first_target:
90
+ headers.append("Locus_Tag")
91
+ self.data['has_locus_tag'] = True
92
+
93
+ if 'gene_name' in first_target or 'feature_name' in first_target:
94
+ headers.append("Gene_Name")
95
+ self.data['has_gene_name'] = True
96
+
97
+ return headers
98
+
99
+ except Exception as e:
100
+ self.logger.error(f"Error getting view targets headers: {str(e)}")
101
+ raise
102
+
103
+ def get_file_extension(self, delimiter: str) -> str:
104
+ """Get appropriate file extension based on delimiter"""
105
+ if delimiter == ",":
106
+ return ".csv"
107
+ elif delimiter == r"\t":
108
+ return ".tsv"
109
+ return ".txt"
110
+
111
+ def get_full_path(self, directory: str, filename: str, delimiter: str) -> str:
112
+ """Construct full file path"""
113
+ if '.' in filename:
114
+ return os.path.join(directory, filename)
115
+ extension = self.get_file_extension(delimiter)
116
+ return os.path.join(directory, filename + extension)
src/models/FindTargetsModel.py CHANGED
@@ -4,12 +4,14 @@ from models.CSPRparser import CSPRparser
4
  from models.AnnotationParser import AnnotationParser
5
  import os
6
  from functools import lru_cache
 
 
7
 
8
  class FindTargetsModel(HomeWindowModel):
9
  def __init__(self, global_settings):
10
  super().__init__(global_settings)
11
  self.results = {}
12
- self._parser_cache = {} # Cache for CSPRparser instances
13
  self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
14
 
15
  def _on_annotation_file_changed(self, new_annotation_file):
@@ -27,26 +29,15 @@ class FindTargetsModel(HomeWindowModel):
27
  def find_targets(self, input_data):
28
  self.global_settings.logger.debug(f"Received input data: {input_data}")
29
 
30
- start_time = time.time()
31
-
32
  organism = input_data['organism']
33
  endo = input_data['endonuclease']
34
  org_files = self.get_organism_to_files()
35
 
36
- # Validate input data
37
- validate_start = time.time()
38
  self._validate_input(organism, endo, org_files)
39
- validate_time = time.time() - validate_start
40
- self.global_settings.logger.debug(f"Validation time: {validate_time:.2f} seconds")
41
 
42
- # Get file path and parser
43
- parser_start = time.time()
44
  file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
45
  parser = self._get_parser(file_path)
46
- parser_time = time.time() - parser_start
47
- self.global_settings.logger.debug(f"Parser initialization time: {parser_time:.2f} seconds")
48
 
49
- # Use dictionary for faster lookup
50
  search_types = {
51
  'feature': self.find_targets_by_feature,
52
  'position': self.find_targets_by_position,
@@ -59,19 +50,11 @@ class FindTargetsModel(HomeWindowModel):
59
  self.global_settings.logger.error(error_msg)
60
  raise ValueError(error_msg)
61
 
62
- # Perform the search
63
- search_start = time.time()
64
  self.results = search_func(parser, input_data)
65
- search_time = time.time() - search_start
66
- self.global_settings.logger.debug(f"Search execution time: {search_time:.2f} seconds")
67
-
68
- total_time = time.time() - start_time
69
- self.global_settings.logger.debug(f"Total find_targets time: {total_time:.2f} seconds")
70
 
71
  return self.results
72
 
73
  def _validate_input(self, organism, endo, org_files):
74
- """Validate input parameters"""
75
  if organism not in org_files:
76
  error_msg = f"Organism '{organism}' not found in the database. Available organisms: {list(org_files.keys())}"
77
  self.global_settings.logger.error(error_msg)
@@ -83,47 +66,37 @@ class FindTargetsModel(HomeWindowModel):
83
  raise ValueError(error_msg)
84
 
85
  def find_targets_by_feature(self, parser, input_data):
86
- """Search for features using the indexed annotation parser"""
87
  try:
88
- start_time = time.time()
89
-
90
- # Get annotation file from input data or global settings
91
  annotation_file = (input_data.get('annotation_file') or
92
  self.global_settings.get_current_annotation_file())
93
 
94
  search_query = input_data['search_query'].strip()
95
 
96
- # Create new annotation parser instance
97
- parser_start = time.time()
98
  annotation_parser = AnnotationParser(self.global_settings)
99
  annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
100
  annotation_parser.set_annotation_file(annotation_file_path)
101
- parser_time = time.time() - parser_start
102
- self.global_settings.logger.debug(f"Annotation parser initialization time: {parser_time:.2f} seconds")
103
 
104
- # Use indexed search
105
- search_start = time.time()
106
  results_list = annotation_parser.genbank_search([search_query])
107
- search_time = time.time() - search_start
108
- self.global_settings.logger.debug(f"Genbank search time: {search_time:.2f} seconds")
109
 
110
- # Format results
111
- format_start = time.time()
112
  formatted_results = []
 
 
 
 
 
 
 
113
  for record_id, feature_info in results_list:
114
- # Extract start and end from feature_location
115
  location = feature_info['feature_location']
116
  start_end = location.split('(')[0] # Get part before the strand
117
  start, end = map(int, start_end.split(':'))
118
 
119
- # Extract chromosome number from record_id (e.g., "NZ_CP132594.1" -> "1")
120
- chrom_num = record_id.split('.')[-1] if '.' in record_id else '1'
121
 
122
- # Create target info with feature_id and chromosome number
123
  target_info = {
124
  'feature_type': 'CDS',
125
- 'chromosome': chrom_num, # Use chromosome number
126
- 'full_chromosome': record_id, # Store full chromosome name for reference
127
  'feature_id': feature_info['feature_id'],
128
  'feature_name': feature_info['feature_name'],
129
  'feature_description': feature_info['feature_description'],
@@ -134,22 +107,10 @@ class FindTargetsModel(HomeWindowModel):
134
  'endonuclease': input_data['endonuclease']
135
  }
136
 
137
- # Debug log the target info
138
  self.global_settings.logger.debug(f"Created target info: {target_info}")
139
 
140
  formatted_results.append(target_info)
141
 
142
- format_time = time.time() - format_start
143
- self.global_settings.logger.debug(f"Result formatting time: {format_time:.2f} seconds")
144
-
145
- # Debug log sample results
146
- if formatted_results:
147
- self.global_settings.logger.debug(f"Sample formatted result: {formatted_results[0]}")
148
- self.global_settings.logger.debug(f"Feature IDs present: {[r['feature_id'] for r in formatted_results[:5]]}")
149
-
150
- total_time = time.time() - start_time
151
- self.global_settings.logger.debug(f"Total find_targets_by_feature time: {total_time:.2f} seconds")
152
-
153
  return formatted_results
154
 
155
  except Exception as e:
@@ -157,27 +118,243 @@ class FindTargetsModel(HomeWindowModel):
157
  raise
158
 
159
  def find_targets_by_position(self, parser, input_data):
160
- search_query = input_data['search_query']
161
- chrom, start, end = map(int, search_query.split(','))
162
- pos_tuple = (chrom, start - 1, end) # Adjust for 0-based indexing
163
-
164
- targets = parser.read_targets(f"position_{chrom}_{start}_{end}", pos_tuple, input_data['endonuclease'])
165
- return self._format_results(targets)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
  def find_targets_by_sequence(self, parser, input_data):
168
- search_query = input_data['search_query'].upper()
169
- annotation_file = input_data['annotation_file']
170
-
171
- self.annotation_parser.annotationFileName = os.path.join(self.global_settings.get_db_path(), annotation_file)
172
- sequence_info = self.annotation_parser.get_sequence_info(search_query)
173
-
174
- if sequence_info:
175
- chrom, start, end = sequence_info
176
- pos_tuple = (chrom, start - 1, end)
177
- targets = parser.read_targets(f"sequence_{start}_{end}", pos_tuple, input_data['endonuclease'])
178
- return self._format_results(targets)
179
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  return []
 
 
 
 
 
181
 
182
  def _format_results(self, targets):
183
  formatted_results = []
 
4
  from models.AnnotationParser import AnnotationParser
5
  import os
6
  from functools import lru_cache
7
+ import traceback
8
+ from Bio import SeqIO
9
 
10
  class FindTargetsModel(HomeWindowModel):
11
  def __init__(self, global_settings):
12
  super().__init__(global_settings)
13
  self.results = {}
14
+ self._parser_cache = {}
15
  self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
16
 
17
  def _on_annotation_file_changed(self, new_annotation_file):
 
29
  def find_targets(self, input_data):
30
  self.global_settings.logger.debug(f"Received input data: {input_data}")
31
 
 
 
32
  organism = input_data['organism']
33
  endo = input_data['endonuclease']
34
  org_files = self.get_organism_to_files()
35
 
 
 
36
  self._validate_input(organism, endo, org_files)
 
 
37
 
 
 
38
  file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
39
  parser = self._get_parser(file_path)
 
 
40
 
 
41
  search_types = {
42
  'feature': self.find_targets_by_feature,
43
  'position': self.find_targets_by_position,
 
50
  self.global_settings.logger.error(error_msg)
51
  raise ValueError(error_msg)
52
 
 
 
53
  self.results = search_func(parser, input_data)
 
 
 
 
 
54
 
55
  return self.results
56
 
57
  def _validate_input(self, organism, endo, org_files):
 
58
  if organism not in org_files:
59
  error_msg = f"Organism '{organism}' not found in the database. Available organisms: {list(org_files.keys())}"
60
  self.global_settings.logger.error(error_msg)
 
66
  raise ValueError(error_msg)
67
 
68
  def find_targets_by_feature(self, parser, input_data):
 
69
  try:
 
 
 
70
  annotation_file = (input_data.get('annotation_file') or
71
  self.global_settings.get_current_annotation_file())
72
 
73
  search_query = input_data['search_query'].strip()
74
 
 
 
75
  annotation_parser = AnnotationParser(self.global_settings)
76
  annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
77
  annotation_parser.set_annotation_file(annotation_file_path)
 
 
78
 
 
 
79
  results_list = annotation_parser.genbank_search([search_query])
 
 
80
 
 
 
81
  formatted_results = []
82
+
83
+ chrom_mapping = {}
84
+ chrom_count = 0
85
+ for record in SeqIO.parse(annotation_file_path, "genbank"):
86
+ chrom_count += 1
87
+ chrom_mapping[record.id] = str(chrom_count)
88
+
89
  for record_id, feature_info in results_list:
 
90
  location = feature_info['feature_location']
91
  start_end = location.split('(')[0] # Get part before the strand
92
  start, end = map(int, start_end.split(':'))
93
 
94
+ chrom_num = chrom_mapping.get(record_id, '1')
 
95
 
 
96
  target_info = {
97
  'feature_type': 'CDS',
98
+ 'chromosome': chrom_num,
99
+ 'full_chromosome': record_id,
100
  'feature_id': feature_info['feature_id'],
101
  'feature_name': feature_info['feature_name'],
102
  'feature_description': feature_info['feature_description'],
 
107
  'endonuclease': input_data['endonuclease']
108
  }
109
 
 
110
  self.global_settings.logger.debug(f"Created target info: {target_info}")
111
 
112
  formatted_results.append(target_info)
113
 
 
 
 
 
 
 
 
 
 
 
 
114
  return formatted_results
115
 
116
  except Exception as e:
 
118
  raise
119
 
120
  def find_targets_by_position(self, parser, input_data):
121
+ try:
122
+ queries = input_data['search_query'].strip().split('\n')
123
+ all_results = []
124
+
125
+ for query in queries:
126
+ try:
127
+ chrom, start, end = map(int, query.strip().split(','))
128
+
129
+ # Get full chromosome ID by counting carets
130
+ full_chrom = None
131
+ chrom_count = 0
132
+
133
+ # Get annotation file path
134
+ annotation_file = self.global_settings.get_current_annotation_file()
135
+ annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
136
+
137
+ # Find the full chromosome ID by position
138
+ for record in SeqIO.parse(annotation_path, "genbank"):
139
+ chrom_count += 1
140
+ if chrom_count == chrom: # Match based on position
141
+ full_chrom = record.id
142
+ self.logger.debug(f"Found chromosome {chrom} as {full_chrom}")
143
+ break
144
+
145
+ if not full_chrom:
146
+ self.logger.warning(f"Could not find chromosome at position {chrom}")
147
+ continue
148
+
149
+ # Create target info with proper formatting
150
+ position_name = f"chrom {chrom}, start: {start}, end: {end}"
151
+ target_info = [{
152
+ 'start': start,
153
+ 'end': end,
154
+ 'feature_id': position_name,
155
+ 'feature_name': position_name,
156
+ 'chromosome': str(chrom), # Keep chromosome number for CSPR lookup
157
+ 'full_chromosome': full_chrom # Store full ID for sequence lookup
158
+ }]
159
+
160
+ # Get targets using batch processing
161
+ self.logger.debug(f"Searching for targets in chromosome {chrom} from {start} to {end}")
162
+ targets = parser.read_targets_batch(str(chrom), target_info, input_data['endonuclease'])
163
+
164
+ if targets:
165
+ self.logger.debug(f"Found {len(targets)} raw targets")
166
+ filtered_targets = []
167
+ guide_length = 23 # Length of guide RNA
168
+
169
+ for target in targets:
170
+ target_pos = int(target['position'])
171
+ target_end = target_pos
172
+
173
+ # Include target if:
174
+ # 1. Target start position is within range
175
+ # 2. Target end position is within or equal to end position
176
+ if start <= target_pos and target_end <= end + 1:
177
+ filtered_targets.append(target)
178
+
179
+ self.logger.debug(f"Filtered to {len(filtered_targets)} targets within range")
180
+
181
+ # Get sequence for this region
182
+ sequence = self._get_sequence_for_position(chrom, start, end)
183
+
184
+ # Format results
185
+ for target in filtered_targets:
186
+ result = {
187
+ 'feature_type': 'Position',
188
+ 'chromosome': str(chrom),
189
+ 'feature_id': position_name,
190
+ 'feature_name': position_name,
191
+ 'feature_description': position_name,
192
+ 'location': target['location'],
193
+ 'start': start,
194
+ 'end': end,
195
+ 'strand': target['strand'],
196
+ 'sequence': target['sequence'],
197
+ 'pam': target['pam'],
198
+ 'score': target['score'],
199
+ 'endonuclease': target['endonuclease'],
200
+ 'gene_sequence': sequence
201
+ }
202
+ all_results.append(result)
203
+
204
+ self.logger.debug(f"Added {len(filtered_targets)} formatted results")
205
+ else:
206
+ self.logger.warning(f"No targets found for query: {query}")
207
+
208
+ except Exception as e:
209
+ self.logger.error(f"Error processing query {query}: {str(e)}")
210
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
211
+ continue
212
+
213
+ self.logger.debug(f"Total results found: {len(all_results)}")
214
+ return all_results
215
+
216
+ except Exception as e:
217
+ self.logger.error(f"Error in find_targets_by_position: {str(e)}")
218
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
219
+ raise
220
+
221
+ def _get_sequence_for_position(self, chrom, start, end):
222
+ """Get sequence for a given position with proper padding handling"""
223
+ try:
224
+ if not hasattr(self, 'annotation_parser') or self.annotation_parser is None:
225
+ self.annotation_parser = AnnotationParser(self.global_settings)
226
+ annotation_file = self.global_settings.get_current_annotation_file()
227
+ annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
228
+ self.annotation_parser.set_annotation_file(annotation_path)
229
+
230
+ feature_info = {
231
+ 'chromosome': f"NZ_CP032679.{chrom}", # Use full name 'chromosome'
232
+ 'start': start-1, # Use full name 'start'
233
+ 'end': end # Use full name 'end'
234
+ }
235
+
236
+ sequence = self.annotation_parser._get_sequence_for_gene(feature_info)
237
+ if sequence:
238
+ padding = 30
239
+
240
+ # Handle start position padding
241
+ if start == 1:
242
+ # No padding at start if starting at position 1
243
+ five_prime_pad = ""
244
+ main_sequence = sequence[:-(padding if len(sequence) > padding else 0)].upper()
245
+ else:
246
+ five_prime_pad = sequence[:padding].lower() if len(sequence) > padding else ""
247
+ main_sequence = sequence[padding:-padding].upper() if len(sequence) > 60 else sequence.upper()
248
+
249
+ three_prime_pad = sequence[-padding:].lower() if len(sequence) > padding else ""
250
+
251
+ return five_prime_pad + main_sequence + three_prime_pad
252
+
253
+ return None
254
+
255
+ except Exception as e:
256
+ self.logger.error(f"Error getting sequence for position: {str(e)}")
257
+ return None
258
 
259
  def find_targets_by_sequence(self, parser, input_data):
260
+ """Search for targets by sequence"""
261
+ try:
262
+ sequence = input_data['search_query'].strip().upper()
263
+
264
+ # Validate sequence length
265
+ if len(sequence) < 100:
266
+ self.logger.error("Sequence too short")
267
+ return []
268
+
269
+ # Get annotation file
270
+ annotation_file = self.global_settings.get_current_annotation_file()
271
+ if not annotation_file:
272
+ self.logger.error("No annotation file selected")
273
+ return []
274
+
275
+ # Initialize annotation parser
276
+ if not hasattr(self, 'annotation_parser') or self.annotation_parser is None:
277
+ self.annotation_parser = AnnotationParser(self.global_settings)
278
+ annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
279
+ self.annotation_parser.set_annotation_file(annotation_path)
280
+
281
+ # Find sequence in genome
282
+ chrom_count = 0
283
+ for record in SeqIO.parse(self.annotation_parser.annotation_file_name, "genbank"):
284
+ chrom_count += 1 # Count chromosome position by caret
285
+ record_seq = str(record.seq).upper()
286
+ pos = record_seq.find(sequence)
287
+
288
+ if pos != -1:
289
+ # Found the sequence
290
+ start = pos + 1 # 1-based position
291
+ end = start + len(sequence) - 1
292
+
293
+ # Create position name
294
+ position_name = f"chrom {chrom_count}, start: {start}, end: {end}"
295
+
296
+ # Create target info
297
+ target_info = [{
298
+ 'start': start,
299
+ 'end': end,
300
+ 'feature_id': position_name,
301
+ 'feature_name': position_name,
302
+ 'chromosome': str(chrom_count), # Use caret-based chromosome number
303
+ 'full_chromosome': record.id # Store full chromosome ID
304
+ }]
305
+
306
+ # Get targets in this region
307
+ self.logger.debug(f"Found sequence in chromosome {chrom_count} from {start} to {end}")
308
+ targets = parser.read_targets_batch(str(chrom_count), target_info, input_data['endonuclease'])
309
+
310
+ if targets:
311
+ self.logger.debug(f"Found {len(targets)} raw targets")
312
+ filtered_targets = []
313
+ guide_length = 23
314
+
315
+ for target in targets:
316
+ target_pos = int(target['position'])
317
+ target_end = target_pos + guide_length
318
+
319
+ # Include target if within sequence bounds
320
+ if start <= target_pos and target_end <= end + 1:
321
+ filtered_targets.append(target)
322
+
323
+ self.logger.debug(f"Filtered to {len(filtered_targets)} targets within range")
324
+
325
+ # Get sequence with padding
326
+ sequence_with_padding = self._get_sequence_for_position(chrom_count, start, end)
327
+
328
+ # Format results
329
+ all_results = []
330
+ for target in filtered_targets:
331
+ result = {
332
+ 'feature_type': 'Position',
333
+ 'chromosome': str(chrom_count),
334
+ 'feature_id': position_name,
335
+ 'feature_name': position_name,
336
+ 'feature_description': f"Sequence match at {position_name}",
337
+ 'location': target['location'],
338
+ 'start': start,
339
+ 'end': end,
340
+ 'strand': target['strand'],
341
+ 'sequence': target['sequence'],
342
+ 'pam': target['pam'],
343
+ 'score': target['score'],
344
+ 'endonuclease': target['endonuclease'],
345
+ 'gene_sequence': sequence_with_padding
346
+ }
347
+ all_results.append(result)
348
+
349
+ return all_results
350
+
351
+ self.logger.warning("Sequence not found in genome")
352
  return []
353
+
354
+ except Exception as e:
355
+ self.logger.error(f"Error in find_targets_by_sequence: {str(e)}")
356
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
357
+ raise
358
 
359
  def _format_results(self, targets):
360
  formatted_results = []
src/models/GenerateLibraryModel.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from models.CSPRparser import CSPRparser
2
+ from models.HomeWindowModel import HomeWindowModel
3
+ import os
4
+ import re
5
+ import traceback
6
+
7
+ class GenerateLibraryModel(HomeWindowModel):
8
+ def __init__(self, global_settings):
9
+ super().__init__(global_settings)
10
+ self.logger = global_settings.logger
11
+ self.parser = None
12
+ self.targets_data = {}
13
+ self._deleted_targets = {}
14
+
15
+ def initialize_parser(self, cspr_file):
16
+ """Initialize CSPR parser"""
17
+ self.parser = CSPRparser(cspr_file, self.global_settings.get_casper_info_path())
18
+
19
+ def generate_library(self, selected_targets, settings):
20
+ """Generate library with given settings"""
21
+ try:
22
+ self.logger.debug(f"Generating library with settings: {settings}")
23
+
24
+ # Process targets based on settings
25
+ processed_targets = self._process_targets(
26
+ selected_targets,
27
+ settings['min_score'],
28
+ settings['five_prime_seq'],
29
+ settings['target_range_start'],
30
+ settings['target_range_end']
31
+ )
32
+
33
+ # Generate output for each target
34
+ output_data = self._generate_output(
35
+ processed_targets,
36
+ settings['guides_per_gene'],
37
+ settings['space_between_guides']
38
+ )
39
+
40
+ self.logger.debug(f"Output data: {output_data}")
41
+
42
+ # Write output to file
43
+ self._write_output(output_data, settings)
44
+
45
+ return True
46
+
47
+ except Exception as e:
48
+ self.logger.error(f"Error generating library: {str(e)}")
49
+ self.logger.error(traceback.format_exc())
50
+ raise
51
+
52
+ def _process_targets(self, targets, min_score, five_prime_seq, start_range, end_range):
53
+ """Process and filter targets based on criteria"""
54
+ processed = {}
55
+ self._deleted_targets = {} # Store deleted targets for modify parameters option
56
+
57
+ self.logger.debug(f"Processing {len(targets)} targets with min_score={min_score}")
58
+
59
+ for target in targets:
60
+ # Use feature_name (gene name) as key instead of feature_id
61
+ gene_name = target.get('feature_name', target.get('feature_id'))
62
+
63
+ if gene_name not in processed:
64
+ processed[gene_name] = []
65
+ self._deleted_targets[gene_name] = []
66
+
67
+ # Create a copy of the target data
68
+ target_data = target.copy()
69
+
70
+ # Check if target passes filters
71
+ if self._passes_filters(target_data, min_score, five_prime_seq, start_range, end_range):
72
+ processed[gene_name].append(target_data)
73
+ else:
74
+ self._deleted_targets[gene_name].append(target_data)
75
+
76
+ # Sort targets for each gene
77
+ for gene in processed:
78
+ # First sort by score (ascending)
79
+ processed[gene].sort(key=lambda x: float(x['score']))
80
+
81
+ # Then sort by position (ascending)
82
+ processed[gene].sort(key=lambda x: abs(int(x['position'])))
83
+
84
+ # Reverse list if gene is on negative strand
85
+ if processed[gene] and processed[gene][0].get('strand', '+') == '-':
86
+ processed[gene].reverse()
87
+
88
+ return processed
89
+
90
+ def _passes_filters(self, target, min_score, five_prime_seq, start_range, end_range):
91
+ """Check if target passes all filters"""
92
+ try:
93
+ # Score filter - convert score to float and compare
94
+ target_score = float(target.get('score', 0))
95
+ if target_score < min_score:
96
+ self.logger.debug(f"Target failed score filter: {target_score} < {min_score}")
97
+ return False
98
+
99
+ # Poly-T filter
100
+ if re.search("T{5,10}", target['sequence']):
101
+ self.logger.debug(f"Target failed poly-T filter: {target['sequence']}")
102
+ return False
103
+
104
+ # 5' sequence filter
105
+ if five_prime_seq and not target['sequence'].startswith(five_prime_seq.upper()):
106
+ self.logger.debug(f"Target failed 5' sequence filter")
107
+ return False
108
+
109
+ # Range filter
110
+ if start_range != 0 or end_range != 100:
111
+ position_ratio = self._calculate_position_ratio(target)
112
+ if not (start_range/100 <= position_ratio <= end_range/100):
113
+ self.logger.debug(f"Target failed range filter: {position_ratio}")
114
+ return False
115
+
116
+ return True
117
+
118
+ except Exception as e:
119
+ self.logger.error(f"Error in _passes_filters: {str(e)}")
120
+ self.logger.error(f"Target data: {target}")
121
+ return False
122
+
123
+ def _calculate_position_ratio(self, target):
124
+ """Calculate relative position ratio in gene"""
125
+ try:
126
+ # Get gene length from feature info
127
+ gene_length = abs(int(target.get('end', 0)) - int(target.get('start', 0)))
128
+ if gene_length == 0:
129
+ return 0
130
+
131
+ # Calculate position relative to gene start
132
+ target_pos = abs(int(target['position']))
133
+ gene_start = int(target.get('start', 0))
134
+
135
+ # If gene is on reverse strand, flip the position calculation
136
+ if target.get('strand', '+') == '-':
137
+ ratio = (target.get('end', 0) - target_pos) / gene_length
138
+ else:
139
+ ratio = (target_pos - gene_start) / gene_length
140
+
141
+ self.logger.debug(f"Position ratio: {ratio} (pos={target_pos}, start={gene_start}, len={gene_length}, strand={target.get('strand', '+')}")
142
+ return ratio
143
+
144
+ except Exception as e:
145
+ self.logger.error(f"Error calculating position ratio: {str(e)}")
146
+ return 0
147
+
148
+ def _generate_output(self, processed_targets, guides_per_gene, space_between):
149
+ output = {}
150
+
151
+ for gene_id, targets in processed_targets.items():
152
+ output[gene_id] = []
153
+ i = 0
154
+ vec_index = 0
155
+ prev_target = None
156
+
157
+ while i < guides_per_gene:
158
+ if len(targets) == 0 or vec_index >= len(targets):
159
+ break
160
+
161
+ current = targets[vec_index]
162
+
163
+ # Check spacing from previous target
164
+ if prev_target is None or abs(int(current['position']) - int(prev_target['position'])) >= space_between:
165
+ # If current target has better score than previous
166
+ if (prev_target and float(current['score']) > float(prev_target['score'])):
167
+ output[gene_id].pop()
168
+ output[gene_id].append(current)
169
+ else:
170
+ output[gene_id].append(current)
171
+ prev_target = current
172
+ i += 1
173
+
174
+ vec_index += 1
175
+ if vec_index >= len(targets):
176
+ break
177
+
178
+ # Add deleted targets if needed
179
+ if len(output[gene_id]) < guides_per_gene:
180
+ deleted_sorted = sorted(
181
+ self._deleted_targets.get(gene_id, []),
182
+ key=lambda x: (float(x['score']), abs(int(x['position'])))
183
+ )
184
+
185
+ for deleted_target in deleted_sorted:
186
+ if len(output[gene_id]) >= guides_per_gene:
187
+ break
188
+ deleted_target['modified'] = True
189
+ output[gene_id].append(deleted_target)
190
+
191
+ return output
192
+
193
+ def _write_output(self, output_data, settings):
194
+ """Write library to output file"""
195
+ output_file = settings['output_file']
196
+ if not output_file.endswith('.csv'):
197
+ output_file += '.csv'
198
+
199
+ with open(output_file, 'w') as f:
200
+ # Write header
201
+ headers = ['Gene Name', 'Sequence', 'On-Target Score']
202
+ if settings.get('find_off_targets'):
203
+ headers.append('Off-Target Score')
204
+ headers.extend(['Location', 'PAM', 'Strand'])
205
+ f.write(','.join(headers) + '\n')
206
+
207
+ # Write data
208
+ for gene_id, targets in output_data.items():
209
+ if not targets: # Skip genes with no targets
210
+ continue
211
+
212
+ for i, target in enumerate(targets, 1):
213
+ # Use feature_name (gene name) instead of feature_id
214
+ gene_name = target.get('feature_name', gene_id)
215
+
216
+ # Format gene name with index
217
+ tag_id = f"{gene_name}-{i}"
218
+ tag_id = tag_id.replace(',', '') # Remove any commas
219
+
220
+ # If target was modified by parameters, add asterisks
221
+ if settings.get('modify_params') and target.get('modified'):
222
+ tag_id = "**" + tag_id
223
+
224
+ row = [
225
+ tag_id,
226
+ target['sequence'],
227
+ str(target['score'])
228
+ ]
229
+
230
+ if settings.get('find_off_targets'):
231
+ row.append(str(target.get('off_target_score', '')))
232
+
233
+ row.extend([
234
+ str(abs(int(target['position']))),
235
+ target['pam'],
236
+ target['strand'][0] # Just take first character of strand
237
+ ])
238
+
239
+ f.write(','.join(row) + '\n')
src/models/GlobalSettings.py CHANGED
@@ -8,20 +8,19 @@ from PyQt6.QtCore import QSettings, QObject, pyqtSignal
8
  from PyQt6.QtGui import QPalette, QColor
9
  from PyQt6.QtWidgets import QApplication
10
 
11
- from models.DatabaseManager import DatabaseManager
12
  from models.ConfigManager import ConfigManager
13
 
14
  class GlobalSettings(QObject):
15
- db_state_updated = pyqtSignal(bool, str, list) # Combined signal
16
- first_time_startup = pyqtSignal() # New signal
17
  endonuclease_updated = pyqtSignal()
18
- annotation_file_changed = pyqtSignal(str) # New signal for annotation file changes
 
19
 
20
  def __init__(self, app_dir_path):
21
  super().__init__()
22
 
23
  self.app_dir_path = app_dir_path
24
-
25
  self.logger = self._setup_logging()
26
 
27
  self.config_manager = ConfigManager(app_dir_path=self.app_dir_path, logger=self.logger)
@@ -29,10 +28,13 @@ class GlobalSettings(QObject):
29
 
30
  self.is_first_time_startup = self.config_manager.get_env_value('FIRST_TIME_START', 'TRUE').upper() == 'TRUE'
31
 
32
- self._initialize_directories() # Add this line
33
 
34
  self.db_manager = DatabaseManager(self.logger, self.config_manager)
35
- self.db_manager.db_state_updated.connect(self._on_db_state_updated)
 
 
 
36
 
37
  self.CSPR_DB = self.db_manager.get_db_path()
38
  self.algorithms = self.config_manager.get_config_value('algorithms', ["Azimuth 2.0"])
@@ -45,19 +47,65 @@ class GlobalSettings(QObject):
45
  self.initialize_palettes()
46
 
47
  self.main_window = None
48
-
49
  self._current_annotation_file = None
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  def _initialize_directories(self):
 
 
 
 
52
  self.src_dir_path = os.path.join(self.app_dir_path, 'src')
53
- self.ui_dir_path = os.path.join(self.src_dir_path, self.config_manager.get_config_value('paths.ui'))
54
- self.controllers_dir_path = os.path.join(self.src_dir_path, self.config_manager.get_config_value('paths.controllers'))
55
- self.assets_dir_path = os.path.join(self.app_dir_path, self.config_manager.get_config_value('paths.assets'))
56
  self.models_dir_path = os.path.join(self.src_dir_path, 'models')
57
  self.views_dir_path = os.path.join(self.src_dir_path, 'views')
58
  self.utils_dir_path = os.path.join(self.src_dir_path, 'utils')
59
  self.SeqFinder_dir_path = os.path.join(self.src_dir_path, 'SeqFinder')
60
- self.casper_info_path = os.path.join(self.config_manager.config_dir_path, 'CASPERinfo')
 
 
 
 
 
 
 
 
61
 
62
  def _setup_logging(self):
63
  logger = logging.getLogger(__name__)
@@ -81,24 +129,6 @@ class GlobalSettings(QObject):
81
 
82
  return logger
83
 
84
- def get_db_path(self):
85
- return self.db_manager.get_db_path()
86
-
87
- def validate_db_path(self, path):
88
- return self.db_manager.validate_db_path(path)
89
-
90
- def save_db_path(self, path):
91
- return self.db_manager.save_db_path(path)
92
-
93
- def _on_db_state_updated(self, is_valid, message, cspr_files):
94
- self.db_state_updated.emit(is_valid, message, cspr_files)
95
-
96
- def ensure_db_path_exists(self):
97
- self.db_manager.ensure_db_path_exists()
98
-
99
- def adjust_path_for_os(self, path):
100
- return self.db_manager.adjust_path_for_os(path)
101
-
102
  def get_app_dir_path(self):
103
  return self.app_dir_path
104
 
@@ -123,6 +153,9 @@ class GlobalSettings(QObject):
123
  def get_casper_info_path(self):
124
  return self.casper_info_path
125
 
 
 
 
126
  def get_theme(self):
127
  return self.theme
128
 
@@ -180,15 +213,77 @@ class GlobalSettings(QObject):
180
 
181
  @lru_cache(maxsize=None)
182
  def _get_window_class(self, window_name):
183
- module_name = f"controllers.{window_name}Controller"
184
- class_name = f"{window_name}Controller"
185
- module = importlib.import_module(module_name)
186
- return getattr(module, class_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
  def _create_window(self, window_name):
189
- WindowClass = self._get_window_class(window_name)
190
- controller = WindowClass(self)
191
- return controller
 
 
 
 
 
 
 
 
 
192
 
193
  def get_startup_window(self):
194
  if not hasattr(self, '_startup_window'):
@@ -239,9 +334,6 @@ class GlobalSettings(QObject):
239
  def set_main_window(self, main_window):
240
  self.main_window = main_window
241
 
242
- def update_db_state(self):
243
- self.db_manager.check_db_state()
244
-
245
  def _on_env_file_created(self):
246
  self.logger.info("GlobalSettings: _on_env_file_created")
247
  self.is_first_time_startup = True
@@ -290,5 +382,48 @@ class GlobalSettings(QObject):
290
  from controllers.ScoringOptionsController import ScoringOptionsController
291
  return ScoringOptionsController(self, view_targets_controller)
292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  # Global instance
294
  global_settings = None
 
8
  from PyQt6.QtGui import QPalette, QColor
9
  from PyQt6.QtWidgets import QApplication
10
 
11
+ from models.DatabaseManager import DatabaseManager, FileChangeType
12
  from models.ConfigManager import ConfigManager
13
 
14
  class GlobalSettings(QObject):
15
+ first_time_startup = pyqtSignal()
 
16
  endonuclease_updated = pyqtSignal()
17
+ annotation_file_changed = pyqtSignal(str)
18
+ theme_changed = pyqtSignal(str)
19
 
20
  def __init__(self, app_dir_path):
21
  super().__init__()
22
 
23
  self.app_dir_path = app_dir_path
 
24
  self.logger = self._setup_logging()
25
 
26
  self.config_manager = ConfigManager(app_dir_path=self.app_dir_path, logger=self.logger)
 
28
 
29
  self.is_first_time_startup = self.config_manager.get_env_value('FIRST_TIME_START', 'TRUE').upper() == 'TRUE'
30
 
31
+ self._initialize_directories()
32
 
33
  self.db_manager = DatabaseManager(self.logger, self.config_manager)
34
+
35
+ self.db_manager.db_files_changed.connect(self._on_db_files_changed)
36
+ self.db_manager.db_validation_changed.connect(self._on_db_validation_changed)
37
+ self.db_manager.db_state_changed.connect(self._on_db_state_changed)
38
 
39
  self.CSPR_DB = self.db_manager.get_db_path()
40
  self.algorithms = self.config_manager.get_config_value('algorithms', ["Azimuth 2.0"])
 
47
  self.initialize_palettes()
48
 
49
  self.main_window = None
 
50
  self._current_annotation_file = None
51
 
52
+ def _on_db_files_changed(self, changes):
53
+ """Handle database file changes"""
54
+ self.logger.debug(f"Database files changed: {changes}")
55
+ # Components should connect directly to db_manager signals
56
+ # This method is for global-level handling if needed
57
+
58
+ def _on_db_validation_changed(self, is_valid, message):
59
+ """Handle database validation state changes"""
60
+ self.logger.debug(f"Database validation changed - Valid: {is_valid}, Message: {message}")
61
+ # Handle any global-level validation state changes
62
+
63
+ def _on_db_state_changed(self, is_valid, message, changes):
64
+ """Handle combined database state changes"""
65
+ self.logger.debug(f"Database state changed - Valid: {is_valid}, Message: {message}, Changes: {changes}")
66
+ # Handle any global-level state changes
67
+
68
+ def get_db_path(self):
69
+ """Get the current database path"""
70
+ return self.db_manager.get_db_path()
71
+
72
+ def validate_db_path(self, path):
73
+ """Validate the given database path"""
74
+ return self.db_manager.validate_db_path(path)
75
+
76
+ def save_db_path(self, path):
77
+ """Save the database path"""
78
+ return self.db_manager.save_db_path(path)
79
+
80
+ def ensure_db_path_exists(self):
81
+ """Ensure the database path exists"""
82
+ self.db_manager.ensure_db_path_exists()
83
+
84
+ def update_db_state(self):
85
+ """Check and update the database state"""
86
+ self.db_manager.check_db_state()
87
+
88
  def _initialize_directories(self):
89
+ """Initialize application directories"""
90
+ # app_dir_path is already set in __init__
91
+
92
+ # Set up source directory paths
93
  self.src_dir_path = os.path.join(self.app_dir_path, 'src')
94
+ self.ui_dir_path = os.path.join(self.src_dir_path, 'ui')
95
+ self.controllers_dir_path = os.path.join(self.src_dir_path, 'controllers')
 
96
  self.models_dir_path = os.path.join(self.src_dir_path, 'models')
97
  self.views_dir_path = os.path.join(self.src_dir_path, 'views')
98
  self.utils_dir_path = os.path.join(self.src_dir_path, 'utils')
99
  self.SeqFinder_dir_path = os.path.join(self.src_dir_path, 'SeqFinder')
100
+
101
+ # Set up other resource paths
102
+ self.assets_dir_path = os.path.join(self.app_dir_path, 'assets')
103
+ self.config_dir_path = os.path.join(self.app_dir_path, 'config')
104
+ self.casper_info_path = os.path.join(self.config_dir_path, 'CASPERinfo')
105
+ self.off_target_dir_path = os.path.join(self.models_dir_path, 'OffTarget')
106
+
107
+ # Ensure critical directories exist
108
+ os.makedirs(self.config_dir_path, exist_ok=True)
109
 
110
  def _setup_logging(self):
111
  logger = logging.getLogger(__name__)
 
129
 
130
  return logger
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def get_app_dir_path(self):
133
  return self.app_dir_path
134
 
 
153
  def get_casper_info_path(self):
154
  return self.casper_info_path
155
 
156
+ def get_off_target_dir_path(self):
157
+ return self.off_target_dir_path
158
+
159
  def get_theme(self):
160
  return self.theme
161
 
 
213
 
214
  @lru_cache(maxsize=None)
215
  def _get_window_class(self, window_name):
216
+ """Get the controller class with better error handling and dynamic imports"""
217
+ try:
218
+ # Get the application root directory
219
+ if hasattr(sys, 'frozen'):
220
+ root_dir = os.path.join(os.path.dirname(sys.executable), 'src')
221
+ if platform.system() == 'Darwin': # macOS
222
+ root_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(sys.executable))),
223
+ 'Contents', 'Resources', 'src')
224
+ else:
225
+ root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
226
+
227
+ # Add root directory to Python path if not already there
228
+ if root_dir not in sys.path:
229
+ sys.path.insert(0, root_dir)
230
+
231
+ # Import model (optional)
232
+ try:
233
+ model_name = f"{window_name}Model"
234
+ model_file = os.path.join(root_dir, 'models', f"{model_name}.py")
235
+
236
+ if os.path.exists(model_file):
237
+ spec = importlib.util.spec_from_file_location(
238
+ f"models.{model_name}",
239
+ model_file
240
+ )
241
+ model_module = importlib.util.module_from_spec(spec)
242
+ spec.loader.exec_module(model_module)
243
+ sys.modules[f"models.{model_name}"] = model_module
244
+ self.logger.debug(f"Successfully imported model from {model_file}")
245
+ except Exception as e:
246
+ self.logger.warning(f"Could not find model for {window_name}: {str(e)}")
247
+
248
+ # Import controller (required)
249
+ controller_name = f"{window_name}Controller"
250
+ controller_file = os.path.join(root_dir, 'controllers', f"{controller_name}.py")
251
+
252
+ if not os.path.exists(controller_file):
253
+ raise ImportError(f"Controller file not found: {controller_file}")
254
+
255
+ spec = importlib.util.spec_from_file_location(
256
+ f"controllers.{controller_name}",
257
+ controller_file
258
+ )
259
+ controller_module = importlib.util.module_from_spec(spec)
260
+ spec.loader.exec_module(controller_module)
261
+ sys.modules[f"controllers.{controller_name}"] = controller_module
262
+
263
+ class_name = f"{window_name}Controller"
264
+ if not hasattr(controller_module, class_name):
265
+ raise AttributeError(f"Controller module does not contain class {class_name}")
266
+
267
+ self.logger.debug(f"Successfully imported controller from {controller_file}")
268
+ return getattr(controller_module, class_name)
269
+
270
+ except Exception as e:
271
+ self.logger.error(f"Failed to load controller {window_name}: {str(e)}")
272
+ raise ImportError(f"Could not load controller for {window_name}") from e
273
 
274
  def _create_window(self, window_name):
275
+ """Create a window instance with dynamic module loading"""
276
+ try:
277
+ WindowClass = self._get_window_class(window_name)
278
+ controller = WindowClass(self)
279
+
280
+ # Store the reference to prevent garbage collection
281
+ setattr(self, f'_current_{window_name.lower()}_window', controller)
282
+
283
+ return controller
284
+ except Exception as e:
285
+ self.logger.error(f"Error creating window {window_name}: {str(e)}")
286
+ raise
287
 
288
  def get_startup_window(self):
289
  if not hasattr(self, '_startup_window'):
 
334
  def set_main_window(self, main_window):
335
  self.main_window = main_window
336
 
 
 
 
337
  def _on_env_file_created(self):
338
  self.logger.info("GlobalSettings: _on_env_file_created")
339
  self.is_first_time_startup = True
 
382
  from controllers.ScoringOptionsController import ScoringOptionsController
383
  return ScoringOptionsController(self, view_targets_controller)
384
 
385
+ def get_stylesheet(self):
386
+ """Return the base stylesheet for the application"""
387
+ # Implement this method to return a base stylesheet
388
+ pass
389
+
390
+ def get_groupbox_style(self):
391
+ """Return the style for group boxes"""
392
+ # Implement this method to return the group box style
393
+ pass
394
+
395
+ def get_dark_stylesheet(self):
396
+ """Return the dark theme stylesheet"""
397
+ # Implement this method to return the dark theme stylesheet
398
+ pass
399
+
400
+ def get_light_stylesheet(self):
401
+ """Return the light theme stylesheet"""
402
+ # Implement this method to return the light theme stylesheet
403
+ pass
404
+
405
+ def set_theme(self, theme):
406
+ """Set the current theme and notify listeners"""
407
+ self.theme = theme
408
+ self.theme_changed.emit(theme)
409
+
410
+ def get_theme(self):
411
+ """Get the current theme"""
412
+ return self.theme
413
+
414
+ def get_cotargeting_window(self, view_targets_controller=None):
415
+ """Create and return CoTargetingController instance"""
416
+ if not hasattr(self, '_cotargeting_controller'):
417
+ from controllers.CoTargetingController import CoTargetingController
418
+ self._cotargeting_controller = CoTargetingController(self, view_targets_controller)
419
+ return self._cotargeting_controller
420
+
421
+ def get_export_selected_grnas_window(self):
422
+ """Get or create ExportSelectedgRNAs window"""
423
+ if not hasattr(self, '_export_selected_grnas_controller'):
424
+ from controllers.ExportSelectedgRNAsController import ExportSelectedgRNAsController
425
+ self._export_selected_grnas_controller = ExportSelectedgRNAsController(self)
426
+ return self._export_selected_grnas_controller
427
+
428
  # Global instance
429
  global_settings = None
src/models/HomeWindowModel.py CHANGED
@@ -1,38 +1,66 @@
1
  import os
2
  import glob
3
- from typing import Dict, List
4
  from utils.ui import show_error
 
5
 
6
  class HomeWindowModel:
7
  def __init__(self, global_settings):
8
  self.global_settings = global_settings
9
  self.logger = global_settings.get_logger()
10
- self.data = {}
11
- self.settings = {}
12
- self.dbpath = ""
13
- self.inputstring = ""
14
- self.anno_name = ""
15
- self.endo_name = ""
16
- self.org = ""
17
- self.genlib_list = []
18
- self.results_list = []
19
  self.load_data()
20
 
21
  def load_data(self) -> None:
 
22
  try:
23
  self.load_organisms_and_endonuclease()
24
  self.load_annotation_files()
25
- print(f"Organism to files: {self.data['organism_to_files']}")
26
  except Exception as e:
27
  self.logger.error(f"Error loading data: {str(e)}")
28
  show_error(self.global_settings, "Error Loading Data", str(e))
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def load_organisms_and_endonuclease(self) -> None:
 
31
  try:
 
32
  self.data["organism_to_files"] = {}
33
  self.data["organism_to_endonuclease"] = {}
 
34
  cspr_files = glob.glob(os.path.join(self.global_settings.get_db_path(), "*.cspr"))
35
-
36
  for file in cspr_files:
37
  file_name = os.path.basename(file)
38
  file_name_no_ext = file_name[:-5]
@@ -41,38 +69,58 @@ class HomeWindowModel:
41
  with open(file, 'r') as f:
42
  organism = f.readline().strip().replace("GENOME: ", '')
43
 
44
- if organism in self.data["organism_to_files"]:
45
- self.data["organism_to_files"][organism][endonuclease] = [file_name, file_name.replace(".cspr", "_repeats.db")]
46
- else:
47
- self.data["organism_to_files"][organism] = {endonuclease: [file_name, file_name.replace(".cspr", "_repeats.db")]}
 
 
 
48
 
49
- if organism in self.data["organism_to_endonuclease"]:
 
 
 
50
  self.data["organism_to_endonuclease"][organism].append(endonuclease)
51
- else:
52
- self.data["organism_to_endonuclease"][organism] = [endonuclease]
53
 
54
- self.logger.debug("Successfully loaded genome and endonuclease data.")
 
55
  except Exception as e:
56
- self.logger.error(f"Error in load_organisms_and_endonuclease: {str(e)}")
 
57
 
58
  def load_annotation_files(self) -> None:
 
59
  try:
60
- self.data["annotation_files"] = glob.glob(os.path.join(self.global_settings.CSPR_DB, "**", "*.gb*"), recursive=True)
61
- self.data["annotation_files"] = [os.path.basename(file) for file in self.data["annotation_files"]]
62
- self.data["annotation_files"].sort(key=str.lower)
63
- self.data["annotation_files"].append("None")
64
- self.logger.debug("Successfully loaded annotation files.")
 
 
 
 
 
 
 
 
 
65
  except Exception as e:
66
- self.logger.error(f"Error in load_annotation_files: {str(e)}")
 
67
 
68
  def get_organism_to_files(self) -> Dict[str, Dict[str, List[str]]]:
 
69
  return self.data['organism_to_files']
70
 
71
  def get_organism_to_endonuclease(self) -> Dict[str, List[str]]:
 
72
  return self.data.get("organism_to_endonuclease", {})
73
 
74
  def get_annotation_files(self) -> List[str]:
75
- return self.data.get("annotation_files", [])
 
76
 
77
  def find_targets(self, input_data: dict) -> None:
78
  pass
 
1
  import os
2
  import glob
3
+ from typing import Dict, List, Set
4
  from utils.ui import show_error
5
+ from models.DatabaseManager import FileChangeType
6
 
7
  class HomeWindowModel:
8
  def __init__(self, global_settings):
9
  self.global_settings = global_settings
10
  self.logger = global_settings.get_logger()
11
+ self.data = {
12
+ 'organism_to_files': {},
13
+ 'organism_to_endonuclease': {},
14
+ 'annotation_files': set() # Using set for efficient updates
15
+ }
 
 
 
 
16
  self.load_data()
17
 
18
  def load_data(self) -> None:
19
+ """Load all required data"""
20
  try:
21
  self.load_organisms_and_endonuclease()
22
  self.load_annotation_files()
23
+ self.logger.debug("Successfully loaded all home window data")
24
  except Exception as e:
25
  self.logger.error(f"Error loading data: {str(e)}")
26
  show_error(self.global_settings, "Error Loading Data", str(e))
27
 
28
+ def update_for_file_changes(self, changes: Dict[FileChangeType, List[str]]) -> None:
29
+ """Update model data based on file changes"""
30
+ try:
31
+ needs_organism_reload = False
32
+ needs_annotation_reload = False
33
+
34
+ # Check what types of files changed
35
+ if (FileChangeType.CSPR_ADDED in changes or
36
+ FileChangeType.CSPR_REMOVED in changes):
37
+ needs_organism_reload = True
38
+
39
+ if (FileChangeType.GBFF_ADDED in changes or
40
+ FileChangeType.GBFF_REMOVED in changes):
41
+ needs_annotation_reload = True
42
+
43
+ # Reload only what's necessary
44
+ if needs_organism_reload:
45
+ self.load_organisms_and_endonuclease()
46
+
47
+ if needs_annotation_reload:
48
+ self.load_annotation_files()
49
+
50
+ self.logger.debug(f"Updated model data for changes: {changes}")
51
+
52
+ except Exception as e:
53
+ self.logger.error(f"Error updating for file changes: {str(e)}")
54
+
55
  def load_organisms_and_endonuclease(self) -> None:
56
+ """Load organism and endonuclease data from CSPR files"""
57
  try:
58
+ # Clear existing data
59
  self.data["organism_to_files"] = {}
60
  self.data["organism_to_endonuclease"] = {}
61
+
62
  cspr_files = glob.glob(os.path.join(self.global_settings.get_db_path(), "*.cspr"))
63
+
64
  for file in cspr_files:
65
  file_name = os.path.basename(file)
66
  file_name_no_ext = file_name[:-5]
 
69
  with open(file, 'r') as f:
70
  organism = f.readline().strip().replace("GENOME: ", '')
71
 
72
+ # Update organism to files mapping
73
+ if organism not in self.data["organism_to_files"]:
74
+ self.data["organism_to_files"][organism] = {}
75
+ self.data["organism_to_files"][organism][endonuclease] = [
76
+ file_name,
77
+ file_name.replace(".cspr", "_repeats.db")
78
+ ]
79
 
80
+ # Update organism to endonuclease mapping
81
+ if organism not in self.data["organism_to_endonuclease"]:
82
+ self.data["organism_to_endonuclease"][organism] = []
83
+ if endonuclease not in self.data["organism_to_endonuclease"][organism]:
84
  self.data["organism_to_endonuclease"][organism].append(endonuclease)
 
 
85
 
86
+ self.logger.debug(f"Loaded data for {len(self.data['organism_to_files'])} organisms")
87
+
88
  except Exception as e:
89
+ self.logger.error(f"Error loading organisms and endonucleases: {str(e)}")
90
+ raise
91
 
92
  def load_annotation_files(self) -> None:
93
+ """Load annotation files from the database directory"""
94
  try:
95
+ # Get all .gb* files recursively
96
+ annotation_files = glob.glob(
97
+ os.path.join(self.global_settings.get_db_path(), "**", "*.gb*"),
98
+ recursive=True
99
+ )
100
+
101
+ # Process files
102
+ self.data["annotation_files"] = {
103
+ os.path.basename(file) for file in annotation_files
104
+ if not file.endswith('.index') # Exclude index files
105
+ }
106
+
107
+ self.logger.debug(f"Loaded {len(self.data['annotation_files'])} annotation files")
108
+
109
  except Exception as e:
110
+ self.logger.error(f"Error loading annotation files: {str(e)}")
111
+ raise
112
 
113
  def get_organism_to_files(self) -> Dict[str, Dict[str, List[str]]]:
114
+ """Get mapping of organisms to their files"""
115
  return self.data['organism_to_files']
116
 
117
  def get_organism_to_endonuclease(self) -> Dict[str, List[str]]:
118
+ """Get mapping of organisms to their endonucleases"""
119
  return self.data.get("organism_to_endonuclease", {})
120
 
121
  def get_annotation_files(self) -> List[str]:
122
+ """Get list of annotation files"""
123
+ return sorted(self.data.get("annotation_files", set()), key=str.lower)
124
 
125
  def find_targets(self, input_data: dict) -> None:
126
  pass
src/models/MainWindowModel copy.py DELETED
@@ -1,75 +0,0 @@
1
- import os
2
- import glob
3
- from typing import Dict, List, Any
4
- from utils.ui import show_error
5
-
6
- class MainWindowModel():
7
- def __init__(self, global_settings):
8
- self.global_settings = global_settings
9
- self.logger = global_settings.get_logger()
10
- self.data = {}
11
- self.settings = {}
12
- self.dbpath = ""
13
- self.inputstring = ""
14
- self.anno_name = ""
15
- self.endo_name = ""
16
- self.org = ""
17
- self.genlib_list = []
18
- self.results_list = []
19
-
20
- def load_data(self) -> None:
21
- try:
22
- self.load_organisms_and_endonuclease()
23
- self.load_annotation_files()
24
- except Exception as e:
25
- self.logger.error(f"Error loading data: {str(e)}")
26
- show_error(self.global_settings, "Error Loading Data", str(e))
27
-
28
- def load_organisms_and_endonuclease(self) -> None:
29
- try:
30
- self.data["organism_to_files"] = {}
31
- self.data["organism_to_endonuclease"] = {}
32
- cspr_files = glob.glob(os.path.join(self.global_settings.get_database_path(), "*.cspr"))
33
-
34
- for file in cspr_files:
35
- file_name = os.path.basename(file)
36
- file_name_no_ext = file_name[:-5]
37
- endonuclease = file_name_no_ext[file_name_no_ext.rfind("_")+1:]
38
-
39
- with open(file, 'r') as f:
40
- organism = f.readline().strip().replace("GENOME: ", '')
41
-
42
- if organism in self.data["organism_to_files"]:
43
- self.data["organism_to_files"][organism][endonuclease] = [file_name, file_name.replace(".cspr", "_repeats.db")]
44
- else:
45
- self.data["organism_to_files"][organism] = {endonuclease: [file_name, file_name.replace(".cspr", "_repeats.db")]}
46
-
47
- if organism in self.data["organism_to_endonuclease"]:
48
- self.data["organism_to_endonuclease"][organism].append(endonuclease)
49
- else:
50
- self.data["organism_to_endonuclease"][organism] = [endonuclease]
51
-
52
- self.logger.debug("Successfully loaded genome and endonuclease data.")
53
- except Exception as e:
54
- self.logger.error(f"Error in load_organisms_and_endonuclease: {str(e)}")
55
-
56
- def load_annotation_files(self) -> None:
57
- try:
58
- self.data["annotation_files"] = glob.glob(os.path.join(self.global_settings.CSPR_DB, "**", "*.gb*"), recursive=True)
59
- self.data["annotation_files"] = [os.path.basename(file) for file in self.data["annotation_files"]]
60
- self.data["annotation_files"].sort(key=str.lower)
61
- self.data["annotation_files"].append("None")
62
- self.logger.debug("Successfully loaded annotation files.")
63
- except Exception as e:
64
- self.logger.error(f"Error in load_annotation_files: {str(e)}")
65
-
66
- def get_organism_to_files(self) -> Dict[str, Dict[str, List[str]]]:
67
- return self.data.get("organism_to_files", {})
68
-
69
- def get_organism_to_endonuclease(self) -> Dict[str, List[str]]:
70
- return self.data.get("organism_to_endonuclease", {})
71
-
72
- def get_annotation_files(self) -> List[str]:
73
- return self.data.get("annotation_files", [])
74
-
75
- # Add other methods that handle data processing, validation, and storage
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/models/MultitargetingWindowModel.py CHANGED
@@ -1,4 +1,6 @@
1
  import os
 
 
2
 
3
  class MultitargetingWindowModel:
4
  def __init__(self, global_settings):
@@ -36,7 +38,83 @@ class MultitargetingWindowModel:
36
  """Get repeats data for the seeds table"""
37
  if not self.db_file:
38
  raise ValueError("Database file not set. Please select an organism and endonuclease first.")
39
- return self.settings.db_manager.get_repeats_data(self.db_file, self.row_limit)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  def get_seed_data(self, seed):
42
  """Get detailed data for a specific seed"""
@@ -48,15 +126,75 @@ class MultitargetingWindowModel:
48
 
49
  def get_seeds_vs_repeats_data(self):
50
  """Get data for seeds vs repeats plot"""
51
- return self.settings.db_manager.get_seeds_vs_repeats_data(self.db_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  def get_repeats_vs_seeds_data(self):
54
  """Get data for repeats vs seeds plot"""
55
  return self.settings.db_manager.get_repeats_vs_seeds_data(self.db_file)
56
 
57
  def calculate_statistics(self):
58
- """Calculate global statistics"""
59
- return self.settings.db_manager.calculate_statistics(self.db_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  def get_sql_settings(self):
62
  """Get current SQL query settings"""
@@ -130,3 +268,15 @@ class MultitargetingWindowModel:
130
  except Exception as e:
131
  self.logger.error(f"Error getting kstats: {str(e)}")
132
  raise
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import sqlite3
3
+ import statistics
4
 
5
  class MultitargetingWindowModel:
6
  def __init__(self, global_settings):
 
38
  """Get repeats data for the seeds table"""
39
  if not self.db_file:
40
  raise ValueError("Database file not set. Please select an organism and endonuclease first.")
41
+
42
+ try:
43
+ conn = sqlite3.connect(self.db_file)
44
+ c = conn.cursor()
45
+
46
+ # Use row limit in query
47
+ if self.row_limit == -1: # No limit
48
+ query = "SELECT * FROM repeats ORDER BY count DESC;"
49
+ else:
50
+ query = f"SELECT * FROM repeats ORDER BY count DESC LIMIT 0, {self.row_limit};"
51
+
52
+ results = []
53
+ for repeat in c.execute(query):
54
+ # Extract repeat info
55
+ seed = repeat[0]
56
+ chroms = repeat[1].split(",")
57
+ locs = repeat[2].split(",")
58
+ threes = repeat[3].split(",")
59
+ fives = repeat[4].split(",")
60
+ pams = repeat[5].split(",")
61
+ scores = repeat[6].split(",")
62
+ count = repeat[7]
63
+
64
+ # Calculate average repeats per scaffold
65
+ location_repeat_counts = {}
66
+ for chrom in chroms:
67
+ location_repeat_counts[chrom] = location_repeat_counts.get(chrom, 0) + 1
68
+ avg_per_scaffold = sum(location_repeat_counts.values()) / len(location_repeat_counts)
69
+ avg_per_scaffold = float("%.2f" % avg_per_scaffold)
70
+
71
+ # Find majority sequence
72
+ majority_index = 0
73
+ sequences = ""
74
+ consensus_percent = 0
75
+
76
+ if threes[0] == '':
77
+ majority = max(set(fives), key=fives.count)
78
+ majority_index = fives.index(majority)
79
+ sequences = fives[majority_index] + seed
80
+ consensus_percent = (fives.count(fives[majority_index]) / len(fives)) * 100
81
+ elif fives[0] == '':
82
+ majority = max(set(threes), key=threes.count)
83
+ majority_index = threes.index(majority)
84
+ sequences = seed + threes[majority_index]
85
+ consensus_percent = (threes.count(threes[majority_index]) / len(threes)) * 100
86
+ else:
87
+ # Both 3' and 5' present
88
+ combined_seqs = [f"{fives[i]}{threes[i]}" for i in range(len(threes))]
89
+ majority = max(set(combined_seqs), key=combined_seqs.count)
90
+ majority_index = combined_seqs.index(majority)
91
+ sequences = fives[majority_index] + seed + threes[majority_index]
92
+ consensus_percent = (combined_seqs.count(majority) / len(combined_seqs)) * 100
93
+
94
+ # Determine strand
95
+ strand = "+" if int(locs[majority_index]) >= 0 else "-"
96
+
97
+ # Format consensus percent
98
+ consensus_percent = float("%.1f" % consensus_percent)
99
+
100
+ results.append((
101
+ seed, # Seed sequence
102
+ count, # Total repeats
103
+ avg_per_scaffold, # Average repeats per scaffold
104
+ sequences, # Consensus sequence
105
+ consensus_percent, # Consensus percentage
106
+ scores[majority_index], # Score
107
+ pams[majority_index], # PAM
108
+ strand # Strand
109
+ ))
110
+
111
+ c.close()
112
+ conn.close()
113
+ return results
114
+
115
+ except Exception as e:
116
+ self.logger.error(f"Error getting repeats data: {str(e)}")
117
+ raise
118
 
119
  def get_seed_data(self, seed):
120
  """Get detailed data for a specific seed"""
 
126
 
127
  def get_seeds_vs_repeats_data(self):
128
  """Get data for seeds vs repeats plot"""
129
+ try:
130
+ conn = sqlite3.connect(self.db_file)
131
+ c = conn.cursor()
132
+
133
+ # Query to get count of sequences for each repeat count, ordered by count DESC
134
+ # This matches the original implementation
135
+ query = """
136
+ SELECT count, COUNT(count) as cnt
137
+ FROM repeats
138
+ GROUP BY count
139
+ ORDER BY cnt DESC;
140
+ """
141
+
142
+ x_vals = [] # Number of repeats
143
+ y_vals = [] # Number of sequences
144
+
145
+ for row in c.execute(query):
146
+ x_vals.append(row[0]) # count
147
+ y_vals.append(row[1]) # cnt
148
+
149
+ # Sort x_vals after collecting all data, just like in original
150
+ x_vals = sorted(x_vals)
151
+
152
+ c.close()
153
+ conn.close()
154
+
155
+ return {
156
+ 'x_vals': x_vals,
157
+ 'y_vals': y_vals
158
+ }
159
+
160
+ except Exception as e:
161
+ self.logger.error(f"Error getting seeds vs repeats data: {str(e)}")
162
+ return None
163
 
164
  def get_repeats_vs_seeds_data(self):
165
  """Get data for repeats vs seeds plot"""
166
  return self.settings.db_manager.get_repeats_vs_seeds_data(self.db_file)
167
 
168
  def calculate_statistics(self):
169
+ """Calculate statistics for the repeats data"""
170
+ try:
171
+ conn = sqlite3.connect(self.db_file)
172
+ c = conn.cursor()
173
+
174
+ # Get all repeat counts
175
+ counts = []
176
+ for obj in c.execute("SELECT count FROM repeats;"):
177
+ counts.append(obj[0])
178
+
179
+ if not counts:
180
+ return None
181
+
182
+ # Calculate statistics
183
+ stats = {
184
+ 'average': statistics.mean(counts),
185
+ 'mode': statistics.mode(counts),
186
+ 'median': statistics.median(counts),
187
+ 'repeat_count': len(counts)
188
+ }
189
+
190
+ c.close()
191
+ conn.close()
192
+
193
+ return stats
194
+
195
+ except Exception as e:
196
+ self.logger.error(f"Error calculating statistics: {str(e)}")
197
+ raise
198
 
199
  def get_sql_settings(self):
200
  """Get current SQL query settings"""
 
268
  except Exception as e:
269
  self.logger.error(f"Error getting kstats: {str(e)}")
270
  raise
271
+
272
+ def set_row_limit(self, limit):
273
+ """Set the maximum number of rows to return"""
274
+ try:
275
+ self.row_limit = int(limit)
276
+ except ValueError:
277
+ self.logger.error(f"Invalid row limit value: {limit}")
278
+ self.row_limit = 1000 # Reset to default if invalid
279
+
280
+ def get_row_limit(self):
281
+ """Get current row limit setting"""
282
+ return self.row_limit
src/models/NewEndonucleaseModel.py CHANGED
@@ -71,14 +71,14 @@ class NewEndonucleaseModel(QObject):
71
  pam = ','.join([x.strip() for x in pam.split(',')])
72
 
73
  argument_list = [
74
- form_data['endonuclease_organism'],
75
  form_data['endonuclease_abbreviation'],
76
- form_data['endonuclease_CRISPR_type'],
77
  pam,
78
  form_data['endonuclease_five_prime_length'],
79
  form_data['endonuclease_seed_length'],
80
  form_data['endonuclease_three_prime_length'],
81
  form_data['endonuclease_direction'],
 
 
82
  form_data['endonuclease_on_target_scoring'],
83
  form_data['endonuclease_off_target_scoring']
84
  ]
 
71
  pam = ','.join([x.strip() for x in pam.split(',')])
72
 
73
  argument_list = [
 
74
  form_data['endonuclease_abbreviation'],
 
75
  pam,
76
  form_data['endonuclease_five_prime_length'],
77
  form_data['endonuclease_seed_length'],
78
  form_data['endonuclease_three_prime_length'],
79
  form_data['endonuclease_direction'],
80
+ form_data['endonuclease_organism'],
81
+ form_data['endonuclease_CRISPR_type'],
82
  form_data['endonuclease_on_target_scoring'],
83
  form_data['endonuclease_off_target_scoring']
84
  ]
src/models/NewGenomeWindowModel.py CHANGED
@@ -88,7 +88,7 @@ class NewGenomeWindowModel(QObject):
88
  f'{db_path}',
89
  f'{self.settings.get_casper_info_path()}',
90
  f'{file_path}',
91
- f'"{organism_name} {strain}"',
92
  'notes',
93
  f'DATA:{endonuclease_data["endonuclease_on_target_scoring"]}'
94
  ]
@@ -136,7 +136,6 @@ class NewGenomeWindowModel(QObject):
136
  self.current_job_progress = 0
137
  return self.update_total_progress()
138
 
139
-
140
  def validate_fasta_file(self, file_path):
141
  return file_path.lower().endswith(('.fa', '.fna', '.fasta'))
142
 
 
88
  f'{db_path}',
89
  f'{self.settings.get_casper_info_path()}',
90
  f'{file_path}',
91
+ f'{organism_name} {strain}',
92
  'notes',
93
  f'DATA:{endonuclease_data["endonuclease_on_target_scoring"]}'
94
  ]
 
136
  self.current_job_progress = 0
137
  return self.update_total_progress()
138
 
 
139
  def validate_fasta_file(self, file_path):
140
  return file_path.lower().endswith(('.fa', '.fna', '.fasta'))
141
 
src/{OffTargetFolder → models/OffTarget}/OT_Lin RENAMED
File without changes
src/{OffTargetFolder → models/OffTarget}/OT_Mac RENAMED
File without changes
src/{OffTargetFolder → models/OffTarget}/OT_Win.exe RENAMED
File without changes
src/models/OffTarget/local_output.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DETAILED OUTPUT
2
+ CACTTATGACCGGGCAACTT:0.000000
3
+ ACACTTATGACCGGGCAACT:0.080598
4
+ 0.080598,1,-100695,ACAATTACGCCCGGGCAACC
5
+ TCAAAATAGCCCAAGTTGCC:0.000000
6
+ ATTTTGCTACACTTATGACC:0.000000
7
+ AATTTTGCTACACTTATGAC:0.000000
8
+ GGGAATACTCCCTTTTATTG:0.000000
9
+ GCAAAATTATCCTCAATAAA:0.018309
10
+ 0.018309,1,1937923,GCAAAGTTTTCCTCAATATT
11
+ CAAAATTATCCTCAATAAAA:0.107086
12
+ 0.059761,1,2361124,CACATTTACCCTCAATGAAA
13
+ 0.154411,1,4041223,CAAATCTATACTGAATAAAA
14
+ CAGCTACAACCCGTGGCGGA:0.000000
15
+ CCAGCTACAACCCGTGGCGG:0.106259
16
+ 0.106259,1,4257161,ACAACTGCAAGCCGTGGCGG
17
+ CCGCCAGCTACAACCCGTGG:0.000000
18
+ AGGGAGTATTCCCTCCGCCA:0.000000
19
+ GACCCGCCAGCTACAACCCG:0.000000
20
+ GGGAGTATTCCCTCCGCCAC:0.000000
21
+ CCTCCGCCACGGGTTGTAGC:0.129554
22
+ 0.129554,1,-165804,GTTACGCCACGGGTTGTAGA
23
+ CCGCCACGGGTTGTAGCTGG:0.000000
24
+ CGCCACGGGTTGTAGCTGGC:0.000000
25
+ GGACTACCAACGTTCACCAC:0.221431
26
+ 0.234722,1,-469831,TCGCTACCATCGTTCACCAC
27
+ 0.208141,1,2847166,GGAGAACCGACGGTCACCAC
28
+ AGATAGTGTTCGTAATCCAG:0.000000
29
+ CGTAATCCAGTGGTGAACGT:0.000000
src/{OffTargetFolder → models/OffTarget}/sqlite3.dll RENAMED
File without changes
src/models/OffTargetModel.py ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from math import e
2
+ import os
3
+ import platform
4
+ from PyQt6.QtCore import QProcess, pyqtSignal, QObject
5
+ import logging
6
+ import traceback
7
+
8
+ class OffTargetModel(QObject):
9
+ # Update signal to emit tuple of (scores, details)
10
+ results_ready = pyqtSignal(tuple) # Emits (scores_dict, details_dict)
11
+ progress_updated = pyqtSignal(int, str) # Emits (progress_value, status_message)
12
+
13
+ def __init__(self, global_settings):
14
+ super().__init__() # Make sure OffTargetModel inherits from QObject
15
+ self.global_settings = global_settings
16
+ self.logger = global_settings.get_logger()
17
+ self.process = None
18
+ self.organisms_to_files = {}
19
+ self.organisms_to_endos = {}
20
+ self._current_parameters = None
21
+ self._load_organisms_and_endos()
22
+
23
+ def _load_organisms_and_endos(self):
24
+ """Load available organisms and endonucleases from CSPR files"""
25
+ try:
26
+ cspr_files = [f for f in os.listdir(self.global_settings.get_db_path())
27
+ if f.endswith('.cspr')]
28
+
29
+ for file in cspr_files:
30
+ newname = file[:-4]
31
+ endo = newname[newname.rfind("_") + 1:-1]
32
+
33
+ with open(os.path.join(self.global_settings.get_db_path(), file), 'r') as f:
34
+ species = f.readline().strip().replace("GENOME: ", "")
35
+
36
+ # Store file mappings
37
+ if species not in self.organisms_to_files:
38
+ self.organisms_to_files[species] = {}
39
+ self.organisms_to_files[species][endo] = [
40
+ file,
41
+ file.replace(".cspr", "_repeats.db")
42
+ ]
43
+
44
+ # Store endo mappings
45
+ if species not in self.organisms_to_endos:
46
+ self.organisms_to_endos[species] = []
47
+ if endo not in self.organisms_to_endos[species]:
48
+ self.organisms_to_endos[species].append(endo)
49
+
50
+ except Exception as e:
51
+ self.logger.error(f"Error loading organisms and endonucleases: {str(e)}")
52
+ raise
53
+
54
+ def get_organisms(self):
55
+ """Get list of available organisms"""
56
+ return sorted(self.organisms_to_files.keys())
57
+
58
+ def get_endonucleases(self, organism):
59
+ """Get available endonucleases for given organism"""
60
+ return sorted(self.organisms_to_endos.get(organism, []))
61
+
62
+ def get_file_paths(self, organism, endonuclease):
63
+ """Get CSPR and DB file paths for given organism and endonuclease"""
64
+ try:
65
+ files = self.organisms_to_files[organism][endonuclease]
66
+ return {
67
+ 'cspr_file': os.path.join(self.global_settings.get_db_path(), files[0]),
68
+ 'db_file': os.path.join(self.global_settings.get_db_path(), files[1])
69
+ }
70
+ except KeyError:
71
+ self.logger.error(f"No files found for {organism} and {endonuclease}")
72
+ return None
73
+
74
+ def get_program_command(self):
75
+ if platform.system() == 'Windows':
76
+ return 'OT_Win.exe'
77
+ else:
78
+ return 'OT_Lin' if platform.system() == 'Linux' else 'OT_Mac'
79
+
80
+ def start_analysis(self, parameters):
81
+ """Start off-target analysis with given parameters"""
82
+ try:
83
+ self._current_parameters = parameters
84
+ # Create QProcess if not exists
85
+ if not self.process:
86
+ self.process = QProcess()
87
+ # Connect process signals for logging
88
+ self.process.readyReadStandardOutput.connect(self._handle_process_output)
89
+ self.process.readyReadStandardError.connect(self._handle_process_error)
90
+ self.process.finished.connect(self._handle_process_finished)
91
+
92
+ # Get file paths
93
+ files = self.get_file_paths(parameters['organism'], parameters['endonuclease'])
94
+ if not files:
95
+ raise ValueError("Could not get required file paths")
96
+
97
+ # Build command
98
+ program_path, cmd = self._build_command(parameters, files)
99
+
100
+ # Log analysis start and parameters
101
+ self.logger.debug("=== Starting off-target analysis ===")
102
+ self.logger.debug(f"Organism: {parameters['organism']}")
103
+ self.logger.debug(f"Endonuclease: {parameters['endonuclease']}")
104
+ self.logger.debug(f"Number of targets: {len(parameters.get('targets', []))}")
105
+ self.logger.debug(f"Max mismatches: {parameters['max_mismatches']}")
106
+ self.logger.debug(f"Tolerance: {parameters['tolerance']}")
107
+ self.logger.debug(f"Average output: {parameters['average_output']}")
108
+
109
+ # Set working directory
110
+ off_target_dir = self.global_settings.get_off_target_dir_path()
111
+ self.process.setWorkingDirectory(off_target_dir)
112
+
113
+ # Set executable permissions on Unix systems
114
+ if platform.system() != 'Windows':
115
+ import stat
116
+ st = os.stat(program_path)
117
+ os.chmod(program_path, st.st_mode | stat.S_IEXEC)
118
+
119
+ # Verify temp file exists and has content
120
+ temp_path = os.path.join(off_target_dir, 'temp.txt')
121
+ if os.path.exists(temp_path):
122
+ with open(temp_path, 'r') as f:
123
+ content = f.read()
124
+ self.logger.debug(f"Temp file contents:\n{content}")
125
+ else:
126
+ self.logger.error("Temp file does not exist before starting process")
127
+
128
+ # Start process
129
+ self.logger.debug("Starting QProcess with command:")
130
+ self.logger.debug(cmd)
131
+
132
+
133
+ example_cmd = [
134
+ "/Users/admin/Documents/proj/CASPERtest/CASPERapp/src/models/OffTarget/temp.txt",
135
+ "spCas9",
136
+ "/Users/admin/Documents/CASPERdb2/eck_12_spCas9.cspr",
137
+ "/Users/admin/Documents/CASPERdb2/eck_12_spCas9_repeats.db",
138
+ "/Users/admin/Documents/CASPERdb2/testtttt",
139
+ "/Users/admin/Documents/proj/CASPERtest/CASPERapp/config/CASPERinfo",
140
+ "4",
141
+ "0.05",
142
+ "FALSE",
143
+ "TRUE",
144
+ "MATRIX:HSU MATRIX-spCas9-2013"
145
+ ]
146
+
147
+ print(f"cmd: {cmd}")
148
+
149
+ print(f"example_cmd: {example_cmd}")
150
+
151
+
152
+ self.process.start(str(program_path), cmd)
153
+
154
+
155
+
156
+ return True
157
+
158
+ except Exception as e:
159
+ self.logger.error(f"Error starting analysis: {str(e)}")
160
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
161
+ raise
162
+
163
+ def _handle_process_output(self):
164
+ """Handle standard output from process"""
165
+ try:
166
+ output = self.process.readAllStandardOutput().data().decode()
167
+ self.logger.debug(f"Process output: {output}")
168
+
169
+ # Update progress based on output content
170
+ if "Reading in Data" in output:
171
+ self.progress_updated.emit(10, "Reading data...")
172
+ elif "Loading data for algorithm" in output:
173
+ self.progress_updated.emit(25, "Loading algorithm data...")
174
+ elif "Running OffTarget Analysis" in output:
175
+ self.progress_updated.emit(50, "Running analysis...")
176
+ elif "Writing Output" in output:
177
+ self.progress_updated.emit(75, "Writing results...")
178
+ elif "Analysis Complete" in output:
179
+ self.progress_updated.emit(90, "Finalizing...")
180
+
181
+ except Exception as e:
182
+ self.logger.error(f"Error handling process output: {str(e)}")
183
+
184
+ def _handle_process_error(self):
185
+ """Handle standard error from process"""
186
+ try:
187
+ error = self.process.readAllStandardError().data().decode()
188
+ self.logger.error(f"Process error output: {error}")
189
+ except Exception as e:
190
+ self.logger.error(f"Error handling process error output: {str(e)}")
191
+
192
+ def _handle_process_finished(self, exit_code, exit_status):
193
+ """Handle process completion and parse results"""
194
+ try:
195
+ self.logger.debug(f"Process finished with exit code: {exit_code}")
196
+
197
+ if exit_code == 0: # Success
198
+ # Get output file path
199
+ output_path = self._get_output_path(self._current_parameters)
200
+
201
+ if os.path.exists(output_path):
202
+ # Parse results
203
+ results, detailed_results = self._parse_off_target_results(output_path)
204
+ if results is not None:
205
+ # Update progress to 100%
206
+ self.progress_updated.emit(100, "Analysis complete")
207
+ # Emit results signal with both scores and details
208
+ self.results_ready.emit((results, detailed_results))
209
+ self.logger.debug(f"Parsed and emitted {len(results)} off-target results with {len(detailed_results)} detailed results")
210
+ else:
211
+ self.progress_updated.emit(0, "Analysis failed - no results")
212
+ self.results_ready.emit(({}, {})) # Emit empty results
213
+ else:
214
+ self.logger.error(f"Output file not found: {output_path}")
215
+ self.progress_updated.emit(0, "Analysis failed - no output file")
216
+ self.results_ready.emit(({}, {})) # Emit empty results
217
+ else:
218
+ self.logger.error(f"Process failed with exit code: {exit_code}")
219
+ self.progress_updated.emit(0, f"Analysis failed with code {exit_code}")
220
+ self.results_ready.emit(({}, {})) # Emit empty results
221
+
222
+ # Always cleanup temp file after process finishes
223
+ self._cleanup_temp_file()
224
+
225
+ except Exception as e:
226
+ self.logger.error(f"Error in process finished handler: {str(e)}")
227
+ self.progress_updated.emit(0, f"Error: {str(e)}")
228
+ self.results_ready.emit(({}, {})) # Emit empty results
229
+ # Ensure cleanup happens even if there's an error
230
+ self._cleanup_temp_file()
231
+
232
+ def _cleanup_temp_file(self):
233
+ """Clean up temporary input file"""
234
+ try:
235
+ # Get path to temp file
236
+ db_path = self.global_settings.get_db_path()
237
+ temp_path = os.path.join(db_path, 'temp.txt')
238
+
239
+ # Remove temp file if it exists
240
+ if os.path.exists(temp_path):
241
+ os.remove(temp_path)
242
+ self.logger.debug(f"Removed temp file: {temp_path}")
243
+
244
+ except Exception as e:
245
+ self.logger.error(f"Error cleaning up temp file: {str(e)}")
246
+
247
+ def _parse_off_target_results(self, file_path):
248
+ """Parse off-target analysis results file"""
249
+ try:
250
+ results = {}
251
+ detailed_results = {}
252
+ current_sequence = None
253
+ details = []
254
+
255
+ with open(file_path, 'r') as f:
256
+ lines = f.readlines()
257
+
258
+ output_type = lines[0].strip()
259
+
260
+ # Process based on output type
261
+ if output_type == "DETAILED OUTPUT":
262
+ for line in lines[1:]: # Skip header
263
+ line = line.strip()
264
+ if not line:
265
+ continue
266
+
267
+ if ':' in line: # This is a sequence line
268
+ # Save previous sequence details if they exist
269
+ if current_sequence and details:
270
+ detailed_results[current_sequence] = details
271
+
272
+ # Start new sequence
273
+ parts = line.split(':')
274
+ current_sequence = parts[0].strip()
275
+ try:
276
+ score = float(parts[1].strip())
277
+ results[current_sequence] = score
278
+ except ValueError:
279
+ self.logger.warning(f"Invalid score format in line: {line}")
280
+ details = []
281
+ else: # This is a detail line
282
+ details.append(line)
283
+
284
+ # Don't forget the last sequence
285
+ if current_sequence and details:
286
+ detailed_results[current_sequence] = details
287
+
288
+ elif output_type == "AVG OUTPUT":
289
+ for line in lines[1:]: # Skip header
290
+ line = line.strip()
291
+ if not line:
292
+ continue
293
+
294
+ parts = line.split(':')
295
+ if len(parts) == 2:
296
+ sequence = parts[0].strip()
297
+ try:
298
+ score = float(parts[1].strip())
299
+ results[sequence] = score
300
+ except ValueError:
301
+ self.logger.warning(f"Invalid score format in line: {line}")
302
+
303
+ # Return both results and detailed_results
304
+ return results, detailed_results
305
+
306
+ except Exception as e:
307
+ self.logger.error(f"Error parsing off-target results: {str(e)}")
308
+ return None, None
309
+
310
+ def _build_command(self, parameters, files):
311
+ """Build command string for off-target analysis"""
312
+ try:
313
+ off_target_dir = self.global_settings.get_off_target_dir_path()
314
+
315
+ # Get executable path based on platform
316
+ if platform.system() == 'Windows':
317
+ exe_name = 'OT_Win.exe'
318
+ elif platform.system() == 'Linux':
319
+ exe_name = 'OT_Lin'
320
+ else:
321
+ exe_name = 'OT_Mac'
322
+
323
+ db_path = self.global_settings.get_db_path()
324
+
325
+ # Build paths with quotes
326
+ program_path = f'{os.path.join(off_target_dir, exe_name)}'
327
+ temp_path = f'{os.path.join(db_path, "temp.txt")}'
328
+ cspr_path = f'{files["cspr_file"]}'
329
+ db_path = f'{files["db_file"]}'
330
+ output_path = f'{self._get_output_path(parameters)}'
331
+ casper_info_path = f'{self.global_settings.get_casper_info_path()}'
332
+ endo = f'{parameters["endonuclease"]}'
333
+
334
+ # Build command exactly as in old version
335
+ cmd_parts = [
336
+ temp_path,
337
+ endo,
338
+ cspr_path,
339
+ db_path,
340
+ output_path,
341
+ casper_info_path,
342
+ str(parameters['max_mismatches']),
343
+ str(parameters['tolerance']),
344
+ 'FALSE' if parameters['average_output'] else 'TRUE',
345
+ 'TRUE' if parameters['average_output'] else 'FALSE',
346
+ f'{self._get_hsu_value(parameters)}'
347
+ ]
348
+
349
+ return program_path, cmd_parts
350
+
351
+ except Exception as e:
352
+ self.logger.error(f"Error building command: {str(e)}")
353
+ raise
354
+
355
+ def _get_output_path(self, parameters):
356
+ """Get output file path"""
357
+ print(f"parameters.get('output_filename'): {parameters.get('output_filename')}")
358
+ if parameters.get('output_filename'): # If filename exists
359
+ return os.path.join(
360
+ self.global_settings.get_db_path(),
361
+ parameters['output_filename']
362
+ )
363
+ return os.path.join(
364
+ self.global_settings.get_off_target_dir_path(),
365
+ 'local_output.txt'
366
+ )
367
+
368
+ def _get_hsu_value(self, parameters):
369
+ """Get HSU matrix name for endonuclease from CASPERinfo file"""
370
+ try:
371
+ casper_info_path = self.global_settings.get_casper_info_path()
372
+ endo = parameters['endonuclease']
373
+
374
+ with open(casper_info_path, 'r') as f:
375
+ lines = f.readlines()
376
+
377
+ # Find ENDONUCLEASES section
378
+ for i, line in enumerate(lines):
379
+ if line.strip() == "ENDONUCLEASES":
380
+ # Search through endonuclease entries
381
+ for entry_line in lines[i+1:]:
382
+ if entry_line.strip() == "-----------------------------------------------------------":
383
+ break
384
+
385
+ # Parse endonuclease entry
386
+ parts = entry_line.strip().split(';')
387
+ if len(parts) >= 10: # Make sure we have enough parts
388
+ endo_name = parts[1] # The endonuclease name is the second part
389
+ if endo_name == endo:
390
+ # Get full HSU matrix name (last part)
391
+ hsu_matrix = parts[-1]
392
+ # Format as "MATRIX:HSU MATRIX-{endo}-{year}"
393
+ matrix_name = f"MATRIX:{hsu_matrix}"
394
+ self.logger.debug(f"Found HSU matrix {matrix_name} for {endo}")
395
+ return matrix_name
396
+
397
+ # If we didn't find a match, use default
398
+ self.logger.warning(f"Could not find HSU matrix for {endo}, using default")
399
+ return "MATRIX:HSU MATRIX-spCas9-2013" # Default matrix
400
+
401
+ except Exception as e:
402
+ self.logger.warning(f"Error getting HSU matrix: {str(e)}, using default")
403
+ return "MATRIX:HSU MATRIX-spCas9-2013" # Default matrix
404
+
405
+ def stop_analysis(self):
406
+ """Stop running analysis"""
407
+ try:
408
+ if self.process:
409
+ self.process.kill()
410
+ self.process = None
411
+
412
+ # Clean up temp file when analysis is stopped
413
+ self._cleanup_temp_file()
414
+
415
+ except Exception as e:
416
+ self.logger.error(f"Error stopping analysis: {str(e)}")
417
+
418
+ def cleanup(self):
419
+ """Clean up any temporary files"""
420
+ try:
421
+ # Only clean up temp file
422
+ temp_path = os.path.join(
423
+ self.global_settings.get_off_target_dir_path(),
424
+ 'temp.txt'
425
+ )
426
+ if os.path.exists(temp_path):
427
+ os.remove(temp_path)
428
+ self.logger.debug(f"Removed temp file: {temp_path}")
429
+
430
+ # Don't remove local_output.txt here - let it be handled by the process
431
+
432
+ except Exception as e:
433
+ self.logger.error(f"Error in cleanup: {str(e)}")
434
+
435
+ def _write_targets_to_temp(self, targets):
436
+ """Write target sequences to temporary file for analysis"""
437
+ try:
438
+ db_path = self.global_settings.get_db_path()
439
+ temp_path = os.path.join(db_path, 'temp.txt')
440
+
441
+ self.logger.debug(f"Writing targets to temp file: {temp_path}")
442
+
443
+ with open(temp_path, 'w') as f:
444
+ print(targets)
445
+ for target in targets:
446
+ # Format: position;sequence;pam;score;strand
447
+ entry = f"{target['location'].split('-')[0]};{target['sequence']};{target['pam']};{target['score']};{target['strand']}\n"
448
+ f.write(entry)
449
+
450
+ self.logger.debug(f"Successfully wrote {len(targets)} targets to temp file")
451
+
452
+ # Store temp path for cleanup
453
+ self._temp_path = temp_path
454
+
455
+ except Exception as e:
456
+ self.logger.error(f"Error writing targets to temp file: {str(e)}")
457
+ raise
src/models/PopulationAnalysisWindowModel.py CHANGED
@@ -65,6 +65,9 @@ class PopulationAnalysisWindowModel:
65
  # Get organism mappings from database manager
66
  organisms_to_files, organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
67
 
 
 
 
68
  # Process each organism that has this endonuclease
69
  for organism, endos in organisms_to_endos.items():
70
  if endo in endos:
@@ -75,12 +78,18 @@ class PopulationAnalysisWindowModel:
75
  self.logger.warning(f"Database file not found: {db_file}")
76
  continue
77
 
78
- org_files.append((organism, cspr_file, db_file))
79
-
80
- # Store the mapping for later use
81
- index = len(org_files) - 1
82
- self.index_to_cspr[index] = cspr_file
83
- self.index_to_db[index] = db_file
 
 
 
 
 
 
84
 
85
  self.logger.info(f"Found {len(org_files)} organism files")
86
  except Exception as e:
@@ -89,43 +98,79 @@ class PopulationAnalysisWindowModel:
89
  return org_files
90
 
91
  def get_shared_seeds(self, db_files, limit=False):
 
92
  try:
93
- aliases = [f"main{i}" for i in range(1, len(db_files) + 1)]
94
 
95
- new_conn = sqlite3.connect(os.path.join(self.app_dir, "temp_join.db"))
 
 
96
  new_c = new_conn.cursor()
97
- new_c.execute("PRAGMA synchronous = OFF;")
98
- new_c.execute("PRAGMA journal_mode = OFF;")
99
- new_c.execute("PRAGMA locking_mode = EXCLUSIVE;")
100
- new_c.execute("DROP TABLE IF EXISTS repeats;")
101
- new_c.execute("VACUUM;")
102
- new_c.execute("DROP TABLE IF EXISTS join_results;")
103
- new_c.execute("CREATE table join_results (seed TEXT PRIMARY KEY);")
104
-
105
- for i, db_file in enumerate(db_files):
106
- new_c.execute(f"ATTACH DATABASE '{db_file}' AS {aliases[i]};")
107
-
108
- new_c.execute("BEGIN TRANSACTION;")
109
-
110
- sql_inner_join = "INSERT into main.join_results select main1.repeats.seed from main1.repeats "
111
- for i in range(len(aliases[:-1])):
112
- sql_inner_join += f"inner join {aliases[i + 1]}.repeats on {aliases[i]}.repeats.seed = {aliases[i + 1]}.repeats.seed "
113
-
114
- new_c.execute(sql_inner_join)
115
-
116
- if limit:
117
- shared_seeds = new_c.execute("select * from join_results limit 0,1000").fetchall()
118
- else:
119
- shared_seeds = new_c.execute("select count(*) from join_results").fetchall()
120
-
121
- new_c.execute("END TRANSACTION;")
122
- new_c.close()
123
- new_conn.close()
124
-
125
- return [seed[0] for seed in shared_seeds] if limit else shared_seeds[0][0]
126
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  except Exception as e:
128
- show_error(self.global_settings, "Error in get_shared_seeds()", str(e))
 
 
129
  return [] if limit else 0
130
 
131
  def get_seed_data(self, seed, db_files):
@@ -148,22 +193,52 @@ class PopulationAnalysisWindowModel:
148
  return data
149
 
150
  def get_heatmap_data(self, db_files):
 
151
  try:
152
  size = len(db_files)
153
  arr = [[0 for _ in range(size)] for _ in range(size)]
154
 
 
155
  for i, j in itertools.combinations(range(size), 2):
156
- shared_seeds = self.get_shared_seeds([db_files[i], db_files[j]])
157
- arr[i][j] = arr[j][i] = shared_seeds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
 
159
  for i in range(size):
160
  with sqlite3.connect(db_files[i]) as conn:
161
  c = conn.cursor()
162
  arr[i][i] = c.execute("SELECT COUNT(*) FROM repeats").fetchone()[0]
163
 
164
  return arr
 
165
  except Exception as e:
166
- show_error(self.global_settings, "Error generating heatmap data", str(e))
 
 
167
  return []
168
 
169
  def get_seed_locations(self, seeds, db_files):
 
65
  # Get organism mappings from database manager
66
  organisms_to_files, organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
67
 
68
+ # Create a list to sort alphabetically
69
+ sorted_organisms = []
70
+
71
  # Process each organism that has this endonuclease
72
  for organism, endos in organisms_to_endos.items():
73
  if endo in endos:
 
78
  self.logger.warning(f"Database file not found: {db_file}")
79
  continue
80
 
81
+ sorted_organisms.append((organism, cspr_file, db_file))
82
+
83
+ # Sort organisms alphabetically by organism name
84
+ sorted_organisms.sort(key=lambda x: x[0].lower())
85
+
86
+ # Store the sorted results
87
+ for index, (organism, cspr_file, db_file) in enumerate(sorted_organisms):
88
+ org_files.append((organism, cspr_file, db_file))
89
+
90
+ # Store the mapping for later use
91
+ self.index_to_cspr[index] = cspr_file
92
+ self.index_to_db[index] = db_file
93
 
94
  self.logger.info(f"Found {len(org_files)} organism files")
95
  except Exception as e:
 
98
  return org_files
99
 
100
  def get_shared_seeds(self, db_files, limit=False):
101
+ """Get shared seeds between organisms"""
102
  try:
103
+ self.logger.debug(f"Getting shared seeds for {len(db_files)} organisms")
104
 
105
+ # Create temporary database for join operations
106
+ temp_db_path = os.path.join(self.app_dir, "temp_join.db")
107
+ new_conn = sqlite3.connect(temp_db_path)
108
  new_c = new_conn.cursor()
109
+
110
+ # Set pragmas for better performance
111
+ new_c.execute("PRAGMA synchronous = OFF")
112
+ new_c.execute("PRAGMA journal_mode = OFF")
113
+ new_c.execute("PRAGMA locking_mode = EXCLUSIVE")
114
+
115
+ # Clean up any existing tables
116
+ new_c.execute("DROP TABLE IF EXISTS repeats")
117
+ new_c.execute("DROP TABLE IF EXISTS join_results")
118
+ new_c.execute("VACUUM")
119
+
120
+ # Create results table
121
+ new_c.execute("CREATE TABLE join_results (seed TEXT)")
122
+
123
+ try:
124
+ # Attach all databases
125
+ for i, db_file in enumerate(db_files, 1):
126
+ new_c.execute(f"ATTACH DATABASE '{db_file}' AS main{i}")
127
+
128
+ # Start transaction
129
+ new_c.execute("BEGIN TRANSACTION")
130
+
131
+ # Build query to find seeds shared across all organisms
132
+ base_sql = """INSERT into main.join_results
133
+ select main1.repeats.seed from main1.repeats"""
134
+
135
+ joins = []
136
+ for i in range(2, len(db_files) + 1):
137
+ joins.append(
138
+ f"inner join main{i}.repeats on "
139
+ f"main{i-1}.repeats.seed = main{i}.repeats.seed"
140
+ )
141
+
142
+ full_sql = base_sql + " " + " ".join(joins)
143
+ self.logger.debug(f"Executing SQL for shared seeds: {full_sql}")
144
+ new_c.execute(full_sql)
145
+
146
+ # Get results based on limit parameter
147
+ if limit:
148
+ shared_seeds = new_c.execute("SELECT DISTINCT seed FROM join_results LIMIT 1000").fetchall()
149
+ result = [seed[0] for seed in shared_seeds]
150
+ else:
151
+ result = new_c.execute("SELECT COUNT(DISTINCT seed) FROM join_results").fetchone()[0]
152
+
153
+ # Commit and cleanup
154
+ new_c.execute("END TRANSACTION")
155
+ new_c.close()
156
+ new_conn.close()
157
+
158
+ try:
159
+ os.remove(temp_db_path)
160
+ except:
161
+ self.logger.warning("Could not remove temporary database file")
162
+
163
+ self.logger.debug(f"Found {len(result) if limit else result} shared seeds")
164
+ return result
165
+
166
+ except Exception as e:
167
+ new_c.execute("ROLLBACK")
168
+ raise e
169
+
170
  except Exception as e:
171
+ self.logger.error(f"Error getting shared seeds: {str(e)}")
172
+ self.logger.exception("Full traceback:")
173
+ show_error(self.settings, "Error getting shared seeds", str(e))
174
  return [] if limit else 0
175
 
176
  def get_seed_data(self, seed, db_files):
 
193
  return data
194
 
195
  def get_heatmap_data(self, db_files):
196
+ """Get data for heatmap visualization"""
197
  try:
198
  size = len(db_files)
199
  arr = [[0 for _ in range(size)] for _ in range(size)]
200
 
201
+ # Get shared seeds between pairs of organisms
202
  for i, j in itertools.combinations(range(size), 2):
203
+ # Create temporary database for join operations
204
+ temp_db_path = os.path.join(self.app_dir, "temp_join.db")
205
+ new_conn = sqlite3.connect(temp_db_path)
206
+ new_c = new_conn.cursor()
207
+
208
+ try:
209
+ # Attach databases
210
+ new_c.execute(f"ATTACH DATABASE '{db_files[i]}' AS main1")
211
+ new_c.execute(f"ATTACH DATABASE '{db_files[j]}' AS main2")
212
+
213
+ # Count shared seeds
214
+ sql = """SELECT COUNT(DISTINCT main1.repeats.seed)
215
+ FROM main1.repeats
216
+ INNER JOIN main2.repeats
217
+ ON main1.repeats.seed = main2.repeats.seed"""
218
+
219
+ shared_count = new_c.execute(sql).fetchone()[0]
220
+ arr[i][j] = arr[j][i] = shared_count
221
+
222
+ finally:
223
+ new_c.close()
224
+ new_conn.close()
225
+ try:
226
+ os.remove(temp_db_path)
227
+ except:
228
+ pass
229
 
230
+ # Get individual organism seed counts
231
  for i in range(size):
232
  with sqlite3.connect(db_files[i]) as conn:
233
  c = conn.cursor()
234
  arr[i][i] = c.execute("SELECT COUNT(*) FROM repeats").fetchone()[0]
235
 
236
  return arr
237
+
238
  except Exception as e:
239
+ self.logger.error(f"Error generating heatmap data: {str(e)}")
240
+ self.logger.exception("Full traceback:")
241
+ show_error(self.settings, "Error generating heatmap data", str(e))
242
  return []
243
 
244
  def get_seed_locations(self, seeds, db_files):
src/models/StartupWindowModel.py CHANGED
@@ -8,8 +8,8 @@ class StartupWindowModel(QObject):
8
  self.settings = global_settings
9
  self.logger = global_settings.get_logger()
10
 
11
- # Connect to the combined signal from GlobalSettings
12
- self.settings.db_state_updated.connect(self.on_db_state_updated)
13
 
14
  def get_db_path(self):
15
  return self.settings.get_db_path()
 
8
  self.settings = global_settings
9
  self.logger = global_settings.get_logger()
10
 
11
+ # Connect to the DatabaseManager's signals instead of GlobalSettings
12
+ self.settings.db_manager.db_state_changed.connect(self.on_db_state_updated)
13
 
14
  def get_db_path(self):
15
  return self.settings.get_db_path()
src/models/ViewTargetsModel.py CHANGED
@@ -9,18 +9,14 @@ import threading
9
  from collections import defaultdict
10
  import re
11
  import traceback
12
- import time
13
  import logging
14
- from multiprocessing import Pool
15
- from functools import partial
16
- from multiprocessing import cpu_count
17
 
18
  class ViewTargetsModel(HomeWindowModel):
19
  def __init__(self, global_settings):
20
  super().__init__(global_settings)
21
- self.targets = []
22
  self.cspr_parser = None
23
- self.annotation_parser = None # Will be initialized when needed
24
  self.gene_sequence = ""
25
  self.highlighted_sequence = ""
26
  self.gene_info = {}
@@ -33,14 +29,12 @@ class ViewTargetsModel(HomeWindowModel):
33
  self.extended_sequence = ""
34
  self.chromosome = ""
35
 
36
- # Cache structures
37
  self._gene_data_cache = {}
38
  self._sequence_cache = {}
39
  self._parser_cache = {}
40
  self._chromosome_seqs = {}
41
- self._cached_targets = {} # Add cache for targets
42
 
43
- # Connect to annotation file changes
44
  self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
45
 
46
  # Initialize annotation path
@@ -59,7 +53,6 @@ class ViewTargetsModel(HomeWindowModel):
59
  self.global_settings.annotation_file_changed.disconnect(self._on_annotation_file_changed)
60
  self.global_settings.logger.debug("ViewTargetsModel disconnected from annotation file changes")
61
 
62
- # Clear caches
63
  self._gene_data_cache.clear()
64
  self._sequence_cache.clear()
65
  self._parser_cache.clear()
@@ -90,19 +83,15 @@ class ViewTargetsModel(HomeWindowModel):
90
  except Exception as e:
91
  self.logger.error(f"Error in _on_annotation_file_changed: {str(e)}")
92
 
93
- def load_targets(self, selected_targets, organism, endonuclease):
94
- """Fast target loading with minimal file operations"""
95
- total_start = time.time()
96
-
97
  try:
98
- self.logger.debug(f"Starting load_targets with {len(selected_targets)} targets")
99
 
100
- # Store organism and endonuclease for potential reloading
101
  self.organism = organism
102
  self.endonuclease = endonuclease
103
 
104
  # Get CSPR parser from cache or create new one
105
- parser_start = time.time()
106
  cspr_key = f"{organism}_{endonuclease}"
107
  if cspr_key in self._parser_cache:
108
  self.cspr_parser = self._parser_cache[cspr_key]
@@ -118,64 +107,77 @@ class ViewTargetsModel(HomeWindowModel):
118
  self.cspr_parser = CSPRparser(cspr_path, self.global_settings.get_casper_info_path())
119
  self._parser_cache[cspr_key] = self.cspr_parser
120
  self.logger.debug("Created new CSPR parser")
121
- parser_time = time.time() - parser_start
122
- self.logger.debug(f"CSPR parser initialization time: {parser_time:.2f} seconds")
123
 
124
- # Initialize targets and genes
125
- init_start = time.time()
126
- self.targets = []
127
  self.available_genes = set()
128
- init_time = time.time() - init_start
129
- self.logger.debug(f"Initialization time: {init_time:.2f} seconds")
130
 
131
- # Group targets by chromosome
132
- group_start = time.time()
133
- batch_targets = defaultdict(list)
 
 
 
 
 
 
 
 
 
 
 
134
  for target in selected_targets:
135
- chrom = target['chromosome']
 
 
 
 
 
136
  start, end = map(int, target['location'].split('-'))
137
- batch_targets[chrom].append({
138
- 'feature_name': target['feature_name'],
139
- 'feature_id': target['feature_id'], # Include feature_id (locus_tag)
140
- 'start': start,
141
- 'end': end
142
- })
143
- # Store both feature_id and feature_name
144
- self.available_genes.add((target['feature_id'], target['feature_name']))
145
- group_time = time.time() - group_start
146
- self.logger.debug(f"Target grouping time: {group_time:.2f} seconds")
 
 
 
 
147
 
148
- # Process targets by chromosome
149
- process_start = time.time()
150
- target_count = 0
151
- for chrom, targets in batch_targets.items():
152
- batch_start = time.time()
153
- results = self.cspr_parser.read_targets_batch(chrom, targets, endonuclease)
154
- # Add feature_id to each result
155
  for result in results:
156
- # Find matching target to get feature_id
157
- for target in targets:
158
- if (target['start'] <= result['position'] <= target['end'] and
159
- target['feature_name'] == result['feature_name']):
160
- result['feature_id'] = target['feature_id']
161
- break
162
- self.targets.extend(results)
163
- target_count += len(results)
164
- batch_time = time.time() - batch_start
165
- self.logger.debug(f"Chromosome {chrom} processing time: {batch_time:.2f} seconds")
166
- process_time = time.time() - process_start
167
- self.logger.debug(f"Total target processing time: {process_time:.2f} seconds")
168
 
169
- total_time = time.time() - total_start
170
- self.logger.debug(f"Total load_targets execution time: {total_time:.2f} seconds")
171
- self.logger.debug(f"Found {target_count} total CSPR targets")
 
172
 
173
  except Exception as e:
174
- self.logger.error(f"Error in load_targets: {str(e)}")
175
  self.logger.error(f"Stack trace: {traceback.format_exc()}")
176
 
177
  def _get_chromosome_sequence(self, chromosome):
178
- """Get chromosome sequence on demand"""
179
  if not hasattr(self, '_chromosome_seqs'):
180
  self._chromosome_seqs = {}
181
 
@@ -234,9 +236,9 @@ class ViewTargetsModel(HomeWindowModel):
234
  self.logger.error(f"Stack trace: {traceback.format_exc()}")
235
  return None
236
 
237
- def get_targets(self):
238
- """Return all targets with their feature IDs"""
239
- return self.targets
240
 
241
  def get_available_genes(self):
242
  """Get list of available genes with format 'feature_id: feature_name'"""
@@ -250,78 +252,169 @@ class ViewTargetsModel(HomeWindowModel):
250
  self.logger.error(f"Error getting available genes: {str(e)}")
251
  return []
252
 
253
- # ... (other methods remain unchanged)
254
-
255
- def _process_target(self, target):
256
- """Process a single target - moved to separate method for parallel processing"""
257
  try:
258
- # Your existing target processing logic here
259
  # Make sure to handle any shared resources thread-safely
260
  pass
261
  except Exception as e:
262
- logging.error(f"Error processing target: {e}")
263
  return None
264
 
265
- def get_gene_sequence(self, locus_tag):
266
  """Get gene sequence with optimized caching and minimal I/O"""
267
  try:
 
268
  # Check sequence cache first
269
- cache_key = f"{locus_tag}_sequence"
270
  if cache_key in self._sequence_cache:
271
- self.logger.debug(f"Cache hit for gene sequence: {locus_tag}")
272
  return self._sequence_cache[cache_key]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
- # Get gene data which includes location information
275
- print(f"Getting gene data for locus tag: {locus_tag}")
276
- gene_data = self.get_gene_data(locus_tag)
277
- if not gene_data or 'info' not in gene_data:
278
- self.logger.warning(f"No gene data found for locus tag: {locus_tag}")
279
- return None
280
 
281
- # Parse location string (format: "start:end(strand)")
282
- location = gene_data['info']['location']
283
- if ':' not in location:
284
- self.logger.warning(f"Invalid location format: {location}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  return None
286
 
287
- # Extract start and end positions
288
- start = int(location.split(':')[0])
289
- end = int(location.split(':')[1].split('(')[0])
290
- chromosome = gene_data['info']['chromosome']
291
-
292
- # Get sequence from gene_data directly if available
293
- if 'sequence' in gene_data:
294
- sequence = gene_data['sequence']
 
 
 
 
 
295
 
296
- # Add padding (30 bases on each side)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  padding = 30
298
- seq_start = max(0, start - padding)
299
- seq_end = min(len(sequence), end + padding)
300
 
301
- # Get sequence with padding
302
- five_prime_pad = sequence[seq_start:start].lower() if seq_start < start else ""
303
- main_seq = sequence[start:end].upper()
304
- three_prime_pad = sequence[end:seq_end].lower() if end < seq_end else ""
 
 
 
 
305
 
306
- full_sequence = five_prime_pad + main_seq + three_prime_pad
307
 
308
- # Cache the result
309
- result = {
310
- 'sequence': full_sequence,
311
- 'chrom_length': len(sequence),
312
- 'start': start,
313
- 'end': end,
314
- 'padded_start': seq_start,
315
- 'padded_end': seq_end
316
- }
317
- self._sequence_cache[cache_key] = result
318
-
319
- self.logger.debug(f"Retrieved and cached sequence for locus tag {locus_tag} ({len(full_sequence)} bp)")
320
- return result
321
 
 
 
322
  except Exception as e:
323
- self.logger.error(f"Error getting gene sequence: {str(e)}")
324
- self.logger.error(f"Stack trace: {traceback.format_exc()}")
325
  return None
326
 
327
  def get_scoring_options(self):
@@ -347,4 +440,33 @@ class ViewTargetsModel(HomeWindowModel):
347
  self.logger.debug(f"Updated scoring options: {options}")
348
 
349
  except Exception as e:
350
- self.logger.error(f"Error setting scoring options: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  from collections import defaultdict
10
  import re
11
  import traceback
 
12
  import logging
 
 
 
13
 
14
  class ViewTargetsModel(HomeWindowModel):
15
  def __init__(self, global_settings):
16
  super().__init__(global_settings)
17
+ self.guides = []
18
  self.cspr_parser = None
19
+ self.annotation_parser = None
20
  self.gene_sequence = ""
21
  self.highlighted_sequence = ""
22
  self.gene_info = {}
 
29
  self.extended_sequence = ""
30
  self.chromosome = ""
31
 
 
32
  self._gene_data_cache = {}
33
  self._sequence_cache = {}
34
  self._parser_cache = {}
35
  self._chromosome_seqs = {}
36
+ self._cached_guides = {}
37
 
 
38
  self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
39
 
40
  # Initialize annotation path
 
53
  self.global_settings.annotation_file_changed.disconnect(self._on_annotation_file_changed)
54
  self.global_settings.logger.debug("ViewTargetsModel disconnected from annotation file changes")
55
 
 
56
  self._gene_data_cache.clear()
57
  self._sequence_cache.clear()
58
  self._parser_cache.clear()
 
83
  except Exception as e:
84
  self.logger.error(f"Error in _on_annotation_file_changed: {str(e)}")
85
 
86
+ def load_guides(self, selected_targets, organism, endonuclease):
87
+ """Load guides with proper error handling"""
 
 
88
  try:
89
+ self.logger.debug(f"Starting load_guides with {len(selected_targets)} targets")
90
 
 
91
  self.organism = organism
92
  self.endonuclease = endonuclease
93
 
94
  # Get CSPR parser from cache or create new one
 
95
  cspr_key = f"{organism}_{endonuclease}"
96
  if cspr_key in self._parser_cache:
97
  self.cspr_parser = self._parser_cache[cspr_key]
 
107
  self.cspr_parser = CSPRparser(cspr_path, self.global_settings.get_casper_info_path())
108
  self._parser_cache[cspr_key] = self.cspr_parser
109
  self.logger.debug("Created new CSPR parser")
 
 
110
 
111
+ # Initialize guides and genes
112
+ self.guides = []
 
113
  self.available_genes = set()
 
 
114
 
115
+ # Use a set to track unique guide positions
116
+ seen_guides = set()
117
+
118
+ # Create chromosome mapping by counting carets
119
+ chrom_mapping = {}
120
+ chrom_count = 0
121
+ annotation_file = self.global_settings.get_current_annotation_file()
122
+ annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
123
+
124
+ for record in SeqIO.parse(annotation_path, "genbank"):
125
+ chrom_count += 1
126
+ chrom_mapping[record.id] = str(chrom_count)
127
+
128
+ batch_guides = defaultdict(list)
129
  for target in selected_targets:
130
+ # Get chromosome number from mapping if full_chromosome is available
131
+ if 'full_chromosome' in target:
132
+ chrom = chrom_mapping.get(target['full_chromosome'], target['chromosome'])
133
+ else:
134
+ chrom = target['chromosome']
135
+
136
  start, end = map(int, target['location'].split('-'))
137
+
138
+ # Create a unique identifier for this position range
139
+ position_key = f"{chrom}:{start}-{end}"
140
+
141
+ # Only add if we haven't seen this position range before
142
+ if position_key not in seen_guides:
143
+ seen_guides.add(position_key)
144
+ batch_guides[chrom].append({
145
+ 'feature_name': target['feature_name'],
146
+ 'feature_id': target['feature_id'],
147
+ 'start': start,
148
+ 'end': end
149
+ })
150
+ self.available_genes.add((target['feature_id'], target['feature_name']))
151
 
152
+ # Process guides by chromosome
153
+ unique_guides = {} # Use dict to track unique guides by sequence
154
+ for chrom, guides in batch_guides.items():
155
+ results = self.cspr_parser.read_targets_batch(chrom, guides, endonuclease)
156
+
157
+ # Add feature_id to each result and deduplicate
 
158
  for result in results:
159
+ # Create a unique key for each guide
160
+ guide_key = (result['sequence'], result['position'], result['strand'])
161
+
162
+ if guide_key not in unique_guides:
163
+ # Find matching guide to get feature_id
164
+ for target in selected_targets:
165
+ if (target['start'] <= result['position'] <= target['end'] and
166
+ target['feature_name'] == result['feature_name']):
167
+ result['feature_id'] = target['feature_id']
168
+ unique_guides[guide_key] = result
169
+ break
 
170
 
171
+ # Convert unique guides back to list
172
+ self.guides = list(unique_guides.values())
173
+
174
+ self.logger.debug(f"Found {len(self.guides)} unique guides")
175
 
176
  except Exception as e:
177
+ self.logger.error(f"Error in load_guides: {str(e)}")
178
  self.logger.error(f"Stack trace: {traceback.format_exc()}")
179
 
180
  def _get_chromosome_sequence(self, chromosome):
 
181
  if not hasattr(self, '_chromosome_seqs'):
182
  self._chromosome_seqs = {}
183
 
 
236
  self.logger.error(f"Stack trace: {traceback.format_exc()}")
237
  return None
238
 
239
+ def get_guides(self):
240
+ """Return all guides with their feature IDs"""
241
+ return self.guides
242
 
243
  def get_available_genes(self):
244
  """Get list of available genes with format 'feature_id: feature_name'"""
 
252
  self.logger.error(f"Error getting available genes: {str(e)}")
253
  return []
254
 
255
+ def _process_guide(self, guide):
256
+ """Process a single guide - moved to separate method for parallel processing"""
 
 
257
  try:
258
+ # Your existing guide processing logic here
259
  # Make sure to handle any shared resources thread-safely
260
  pass
261
  except Exception as e:
262
+ logging.error(f"Error processing guide: {e}")
263
  return None
264
 
265
+ def get_gene_sequence(self, identifier):
266
  """Get gene sequence with optimized caching and minimal I/O"""
267
  try:
268
+ print(f"Getting gene sequence for identifier: {identifier}")
269
  # Check sequence cache first
270
+ cache_key = f"{identifier}_sequence"
271
  if cache_key in self._sequence_cache:
272
+ self.logger.debug(f"Cache hit for sequence: {identifier}")
273
  return self._sequence_cache[cache_key]
274
+
275
+ # Check if this is a position-based search
276
+ if "chrom" in identifier and "start:" in identifier:
277
+ try:
278
+ # Parse position from the text (format: "chrom X, start: Y, end: Z")
279
+ parts = identifier.split(',')
280
+ chrom = int(parts[0].split('chrom')[1].strip())
281
+ start = int(parts[1].split('start:')[1].strip())
282
+ end = int(parts[2].split('end:')[1].strip())
283
+
284
+ # Get sequence directly using _get_sequence_for_position
285
+ sequence = self._get_sequence_for_position(chrom, start, end)
286
+ if sequence:
287
+ result = {
288
+ 'sequence': sequence,
289
+ 'start': start,
290
+ 'end': end,
291
+ 'chrom_length': len(sequence)
292
+ }
293
+ self._sequence_cache[cache_key] = result
294
+ self.logger.debug(f"Retrieved and cached position sequence ({len(sequence)} bp)")
295
+ return result
296
+
297
+ self.logger.warning(f"No sequence found for position {chrom}:{start}-{end}")
298
+ return None
299
+
300
+ except Exception as e:
301
+ self.logger.error(f"Error parsing position or getting sequence: {str(e)}")
302
+ return None
303
+ else:
304
+ # Regular gene-based search
305
+ self.logger.debug(f"Getting gene data for locus tag: {identifier}")
306
+ gene_data = self.get_gene_data(identifier)
307
+ if not gene_data or 'info' not in gene_data:
308
+ self.logger.warning(f"No gene data found for locus tag: {identifier}")
309
+ return None
310
 
311
+ # Parse location string (format: "start:end(strand)")
312
+ location = gene_data['info']['location']
313
+ if ':' not in location:
314
+ self.logger.warning(f"Invalid location format: {location}")
315
+ return None
 
316
 
317
+ # Extract start and end positions
318
+ start = int(location.split(':')[0])
319
+ end = int(location.split(':')[1].split('(')[0])
320
+
321
+ # Get sequence from gene_data directly if available
322
+ if 'sequence' in gene_data:
323
+ sequence = gene_data['sequence']
324
+ self.logger.debug(f"Got sequence of length: {len(sequence)}")
325
+
326
+ # Format sequence with padding in lowercase
327
+ padding = 30
328
+ padded_start = max(0, start - padding)
329
+ padded_end = min(len(sequence), end + padding)
330
+
331
+ # Split sequence into parts
332
+ five_prime_pad = sequence[:start - padded_start].lower() if start > padded_start else ""
333
+ main_sequence = sequence[start - padded_start:end - padded_start].upper()
334
+ three_prime_pad = sequence[end - padded_start:].lower()
335
+
336
+ # Combine parts
337
+ formatted_sequence = five_prime_pad + main_sequence + three_prime_pad
338
+
339
+ # Cache the result
340
+ result = {
341
+ 'sequence': formatted_sequence,
342
+ 'chrom_length': len(sequence),
343
+ 'start': start,
344
+ 'end': end,
345
+ 'padded_start': padded_start,
346
+ 'padded_end': padded_end
347
+ }
348
+ self._sequence_cache[cache_key] = result
349
+
350
+ self.logger.debug(f"Retrieved and cached sequence for locus tag {identifier} ({len(formatted_sequence)} bp)")
351
+ return result
352
+
353
+ self.logger.warning(f"No sequence data found in gene_data for {identifier}")
354
  return None
355
 
356
+ except Exception as e:
357
+ self.logger.error(f"Error getting gene sequence: {str(e)}")
358
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
359
+ return None
360
+
361
+ def _get_sequence_for_position(self, chrom, start, end):
362
+ """Get sequence for a given position with proper padding handling"""
363
+ try:
364
+ if not hasattr(self, 'annotation_parser') or self.annotation_parser is None:
365
+ self.annotation_parser = AnnotationParser(self.global_settings)
366
+ annotation_file = self.global_settings.get_current_annotation_file()
367
+ annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
368
+ self.annotation_parser.set_annotation_file(annotation_path)
369
 
370
+ # Get the full chromosome ID by counting carets in annotation file
371
+ full_chrom = None
372
+ chrom_count = 0
373
+
374
+ try:
375
+ for record in SeqIO.parse(self.annotation_path, "genbank"):
376
+ chrom_count += 1
377
+ if chrom_count == int(chrom): # Match based on position rather than ID number
378
+ full_chrom = record.id
379
+ self.logger.debug(f"Found chromosome {chrom} as {full_chrom}")
380
+ break
381
+ except Exception as e:
382
+ self.logger.error(f"Error finding chromosome by position: {str(e)}")
383
+ return None
384
+
385
+ if not full_chrom:
386
+ self.logger.warning(f"Could not find chromosome at position {chrom}")
387
+ return None
388
+
389
+ feature_info = {
390
+ 'chromosome': full_chrom,
391
+ 'start': start-1,
392
+ 'end': end
393
+ }
394
+
395
+ self.logger.debug(f"Getting sequence for feature info: {feature_info}")
396
+
397
+ sequence = self.annotation_parser._get_sequence_for_gene(feature_info)
398
+ if sequence:
399
  padding = 30
 
 
400
 
401
+ # Handle start position padding
402
+ if start == 1:
403
+ # No padding at start if starting at position 1
404
+ five_prime_pad = ""
405
+ main_sequence = sequence[:-(padding if len(sequence) > padding else 0)].upper()
406
+ else:
407
+ five_prime_pad = sequence[:padding].lower() if len(sequence) > padding else ""
408
+ main_sequence = sequence[padding:-padding].upper() if len(sequence) > 60 else sequence.upper()
409
 
410
+ three_prime_pad = sequence[-padding:].lower() if len(sequence) > padding else ""
411
 
412
+ return five_prime_pad + main_sequence + three_prime_pad
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
+ return None
415
+
416
  except Exception as e:
417
+ self.logger.error(f"Error getting sequence for position: {str(e)}")
 
418
  return None
419
 
420
  def get_scoring_options(self):
 
440
  self.logger.debug(f"Updated scoring options: {options}")
441
 
442
  except Exception as e:
443
+ self.logger.error(f"Error setting scoring options: {str(e)}")
444
+
445
+ def get_gene_sequence_for_range(self, identifier, start, end):
446
+ try:
447
+ # For feature-based searches
448
+ gene_data = self.get_gene_data(identifier)
449
+ if not gene_data or 'info' not in gene_data:
450
+ self.logger.warning(f"No gene data found for identifier: {identifier}")
451
+ return None
452
+
453
+ # Get chromosome from gene data
454
+ chrom = gene_data['info']['chromosome'].split('.')[-1] # Extract chromosome number
455
+
456
+ # Use _get_sequence_for_position to get sequence with padding
457
+ sequence = self._get_sequence_for_position(int(chrom), start, end)
458
+ if sequence:
459
+ result = {
460
+ 'sequence': sequence,
461
+ 'start': start,
462
+ 'end': end
463
+ }
464
+ return result
465
+
466
+ self.logger.warning(f"No sequence found for range {start}-{end} in chromosome {chrom}")
467
+ return None
468
+
469
+ except Exception as e:
470
+ self.logger.error(f"Error getting gene sequence for range: {str(e)}")
471
+ self.logger.error(f"Stack trace: {traceback.format_exc()}")
472
+ return None
src/ui/cotargeting.ui CHANGED
@@ -20,26 +20,27 @@
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="1" column="0">
24
- <spacer name="horizontalSpacer">
25
- <property name="orientation">
26
- <enum>Qt::Horizontal</enum>
27
- </property>
28
- <property name="sizeType">
29
- <enum>QSizePolicy::Fixed</enum>
30
- </property>
31
- <property name="sizeHint" stdset="0">
32
- <size>
33
- <width>20</width>
34
- <height>20</height>
35
- </size>
36
- </property>
37
- </spacer>
38
- </item>
39
- <item row="1" column="1">
40
  <layout class="QGridLayout" name="gridLayout">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  <item row="2" column="0">
42
- <widget class="QLabel" name="label">
43
  <property name="sizePolicy">
44
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
45
  <horstretch>0</horstretch>
@@ -51,15 +52,8 @@
51
  </property>
52
  </widget>
53
  </item>
54
- <item row="1" column="0" colspan="2">
55
- <widget class="Line" name="line">
56
- <property name="orientation">
57
- <enum>Qt::Horizontal</enum>
58
- </property>
59
- </widget>
60
- </item>
61
  <item row="4" column="0" alignment="Qt::AlignLeft">
62
- <widget class="QPushButton" name="cancel_button">
63
  <property name="minimumSize">
64
  <size>
65
  <width>125</width>
@@ -75,31 +69,14 @@
75
  </widget>
76
  </item>
77
  <item row="0" column="0">
78
- <widget class="QLabel" name="title">
79
  <property name="text">
80
  <string>Co-Targeting</string>
81
  </property>
82
  </widget>
83
  </item>
84
- <item row="3" column="0" colspan="2">
85
- <widget class="QTableWidget" name="endo_table">
86
- <property name="toolTip">
87
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;These are the list of endonucleases available for this organism. &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Select which endonucleases you want shown at the same time.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
88
- </property>
89
- </widget>
90
- </item>
91
- <item row="2" column="1">
92
- <widget class="QLineEdit" name="orgName">
93
- <property name="enabled">
94
- <bool>false</bool>
95
- </property>
96
- <property name="toolTip">
97
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the organism that targets have been searched for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
98
- </property>
99
- </widget>
100
- </item>
101
  <item row="4" column="1" alignment="Qt::AlignRight">
102
- <widget class="QPushButton" name="submit_button">
103
  <property name="minimumSize">
104
  <size>
105
  <width>125</width>
@@ -114,59 +91,17 @@
114
  </property>
115
  </widget>
116
  </item>
 
 
 
 
 
 
 
117
  </layout>
118
  </item>
119
- <item row="1" column="2">
120
- <spacer name="horizontalSpacer_2">
121
- <property name="orientation">
122
- <enum>Qt::Horizontal</enum>
123
- </property>
124
- <property name="sizeType">
125
- <enum>QSizePolicy::Fixed</enum>
126
- </property>
127
- <property name="sizeHint" stdset="0">
128
- <size>
129
- <width>20</width>
130
- <height>20</height>
131
- </size>
132
- </property>
133
- </spacer>
134
- </item>
135
- <item row="2" column="1">
136
- <spacer name="verticalSpacer">
137
- <property name="orientation">
138
- <enum>Qt::Vertical</enum>
139
- </property>
140
- <property name="sizeType">
141
- <enum>QSizePolicy::Fixed</enum>
142
- </property>
143
- <property name="sizeHint" stdset="0">
144
- <size>
145
- <width>20</width>
146
- <height>20</height>
147
- </size>
148
- </property>
149
- </spacer>
150
- </item>
151
- <item row="0" column="1">
152
- <spacer name="verticalSpacer_2">
153
- <property name="orientation">
154
- <enum>Qt::Vertical</enum>
155
- </property>
156
- <property name="sizeType">
157
- <enum>QSizePolicy::Fixed</enum>
158
- </property>
159
- <property name="sizeHint" stdset="0">
160
- <size>
161
- <width>20</width>
162
- <height>20</height>
163
- </size>
164
- </property>
165
- </spacer>
166
- </item>
167
  </layout>
168
  </widget>
169
- <widget class="QStatusBar" name="statusbar"/>
170
  </widget>
171
  <resources/>
172
  <connections/>
 
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
+ <item row="0" column="0">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  <layout class="QGridLayout" name="gridLayout">
25
+ <item row="3" column="0" colspan="2">
26
+ <widget class="QTableWidget" name="tblEndonucleases">
27
+ <property name="toolTip">
28
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;These are the list of endonucleases available for this organism. &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Select which endonucleases you want shown at the same time.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
29
+ </property>
30
+ </widget>
31
+ </item>
32
+ <item row="2" column="1">
33
+ <widget class="QLineEdit" name="ledOrganism">
34
+ <property name="enabled">
35
+ <bool>false</bool>
36
+ </property>
37
+ <property name="toolTip">
38
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the organism that targets have been searched for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
39
+ </property>
40
+ </widget>
41
+ </item>
42
  <item row="2" column="0">
43
+ <widget class="QLabel" name="lblOrganism">
44
  <property name="sizePolicy">
45
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
46
  <horstretch>0</horstretch>
 
52
  </property>
53
  </widget>
54
  </item>
 
 
 
 
 
 
 
55
  <item row="4" column="0" alignment="Qt::AlignLeft">
56
+ <widget class="QPushButton" name="pbtnCancel">
57
  <property name="minimumSize">
58
  <size>
59
  <width>125</width>
 
69
  </widget>
70
  </item>
71
  <item row="0" column="0">
72
+ <widget class="QLabel" name="lblTitle">
73
  <property name="text">
74
  <string>Co-Targeting</string>
75
  </property>
76
  </widget>
77
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  <item row="4" column="1" alignment="Qt::AlignRight">
79
+ <widget class="QPushButton" name="pbtnSubmit">
80
  <property name="minimumSize">
81
  <size>
82
  <width>125</width>
 
91
  </property>
92
  </widget>
93
  </item>
94
+ <item row="1" column="0" colspan="2">
95
+ <widget class="Line" name="line">
96
+ <property name="orientation">
97
+ <enum>Qt::Horizontal</enum>
98
+ </property>
99
+ </widget>
100
+ </item>
101
  </layout>
102
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  </layout>
104
  </widget>
 
105
  </widget>
106
  <resources/>
107
  <connections/>
src/ui/{export_tool.ui → export_selected_gRNAs.ui} RENAMED
@@ -6,8 +6,8 @@
6
  <rect>
7
  <x>0</x>
8
  <y>0</y>
9
- <width>590</width>
10
- <height>345</height>
11
  </rect>
12
  </property>
13
  <property name="font">
@@ -20,35 +20,35 @@
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="2" column="1" colspan="2">
24
- <widget class="QGroupBox" name="gRNA_Options">
25
  <property name="title">
26
  <string>Guide Options</string>
27
  </property>
28
  <layout class="QGridLayout" name="gridLayout_6">
29
  <item row="1" column="0">
30
- <widget class="QLabel" name="label_3">
31
  <property name="text">
32
  <string>5' Leading Sequence:</string>
33
  </property>
34
  </widget>
35
  </item>
36
  <item row="1" column="1">
37
- <widget class="QLineEdit" name="leading_seq">
38
  <property name="placeholderText">
39
  <string>E.g. promoter, tRNA, homology overhang, etc.</string>
40
  </property>
41
  </widget>
42
  </item>
43
  <item row="2" column="1">
44
- <widget class="QLineEdit" name="trailing_seq">
45
  <property name="placeholderText">
46
  <string>E.g. gRNA scaffold, terminator, etc.</string>
47
  </property>
48
  </widget>
49
  </item>
50
  <item row="2" column="0">
51
- <widget class="QLabel" name="label_4">
52
  <property name="text">
53
  <string>3' Trailing Sequence:</string>
54
  </property>
@@ -57,21 +57,21 @@
57
  </layout>
58
  </widget>
59
  </item>
60
- <item row="1" column="1" colspan="2">
61
- <widget class="QGroupBox" name="export_Options">
62
  <property name="title">
63
  <string>Export Options</string>
64
  </property>
65
  <layout class="QGridLayout" name="gridLayout_3">
66
  <item row="0" column="0">
67
- <widget class="QLabel" name="label">
68
  <property name="text">
69
- <string>File Location:</string>
70
  </property>
71
  </widget>
72
  </item>
73
  <item row="1" column="0">
74
- <widget class="QLabel" name="label_2">
75
  <property name="sizePolicy">
76
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
77
  <horstretch>0</horstretch>
@@ -84,14 +84,14 @@
84
  </widget>
85
  </item>
86
  <item row="1" column="2">
87
- <widget class="QLabel" name="label_5">
88
  <property name="text">
89
  <string>Delimiter:</string>
90
  </property>
91
  </widget>
92
  </item>
93
  <item row="1" column="1">
94
- <widget class="QLineEdit" name="filename_line_edit">
95
  <property name="toolTip">
96
  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Please write the file name you want.&lt;/p&gt;&lt;p&gt;Note: it is not necessary to add the file extension.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
97
  </property>
@@ -101,7 +101,7 @@
101
  </widget>
102
  </item>
103
  <item row="1" column="3">
104
- <widget class="QComboBox" name="delimBox">
105
  <item>
106
  <property name="text">
107
  <string>,</string>
@@ -125,7 +125,7 @@
125
  </widget>
126
  </item>
127
  <item row="0" column="3">
128
- <widget class="QPushButton" name="browse_button">
129
  <property name="sizePolicy">
130
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
131
  <horstretch>0</horstretch>
@@ -141,7 +141,7 @@
141
  </widget>
142
  </item>
143
  <item row="0" column="1" colspan="2">
144
- <widget class="QLineEdit" name="fileLocation_line_edit">
145
  <property name="readOnly">
146
  <bool>true</bool>
147
  </property>
@@ -153,8 +153,8 @@
153
  </layout>
154
  </widget>
155
  </item>
156
- <item row="3" column="2" alignment="Qt::AlignRight">
157
- <widget class="QPushButton" name="export_button">
158
  <property name="sizePolicy">
159
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
160
  <horstretch>0</horstretch>
@@ -168,12 +168,12 @@
168
  </size>
169
  </property>
170
  <property name="text">
171
- <string>Export</string>
172
  </property>
173
  </widget>
174
  </item>
175
- <item row="3" column="1" alignment="Qt::AlignLeft">
176
- <widget class="QPushButton" name="cancel_button">
177
  <property name="sizePolicy">
178
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
179
  <horstretch>0</horstretch>
@@ -187,77 +187,12 @@
187
  </size>
188
  </property>
189
  <property name="text">
190
- <string>Cancel</string>
191
  </property>
192
  </widget>
193
  </item>
194
- <item row="1" column="3" rowspan="2">
195
- <spacer name="horizontalSpacer_3">
196
- <property name="orientation">
197
- <enum>Qt::Horizontal</enum>
198
- </property>
199
- <property name="sizeType">
200
- <enum>QSizePolicy::Fixed</enum>
201
- </property>
202
- <property name="sizeHint" stdset="0">
203
- <size>
204
- <width>20</width>
205
- <height>20</height>
206
- </size>
207
- </property>
208
- </spacer>
209
- </item>
210
- <item row="1" column="0" rowspan="2">
211
- <spacer name="horizontalSpacer_2">
212
- <property name="orientation">
213
- <enum>Qt::Horizontal</enum>
214
- </property>
215
- <property name="sizeType">
216
- <enum>QSizePolicy::Fixed</enum>
217
- </property>
218
- <property name="sizeHint" stdset="0">
219
- <size>
220
- <width>20</width>
221
- <height>20</height>
222
- </size>
223
- </property>
224
- </spacer>
225
- </item>
226
- <item row="0" column="1" colspan="2">
227
- <spacer name="verticalSpacer_2">
228
- <property name="orientation">
229
- <enum>Qt::Vertical</enum>
230
- </property>
231
- <property name="sizeType">
232
- <enum>QSizePolicy::Fixed</enum>
233
- </property>
234
- <property name="sizeHint" stdset="0">
235
- <size>
236
- <width>20</width>
237
- <height>20</height>
238
- </size>
239
- </property>
240
- </spacer>
241
- </item>
242
- <item row="5" column="1" colspan="2">
243
- <spacer name="verticalSpacer">
244
- <property name="orientation">
245
- <enum>Qt::Vertical</enum>
246
- </property>
247
- <property name="sizeType">
248
- <enum>QSizePolicy::Fixed</enum>
249
- </property>
250
- <property name="sizeHint" stdset="0">
251
- <size>
252
- <width>20</width>
253
- <height>20</height>
254
- </size>
255
- </property>
256
- </spacer>
257
- </item>
258
  </layout>
259
  </widget>
260
- <widget class="QStatusBar" name="statusbar"/>
261
  </widget>
262
  <resources/>
263
  <connections/>
 
6
  <rect>
7
  <x>0</x>
8
  <y>0</y>
9
+ <width>960</width>
10
+ <height>916</height>
11
  </rect>
12
  </property>
13
  <property name="font">
 
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
+ <item row="1" column="0" colspan="2">
24
+ <widget class="QGroupBox" name="grpGuideOptions">
25
  <property name="title">
26
  <string>Guide Options</string>
27
  </property>
28
  <layout class="QGridLayout" name="gridLayout_6">
29
  <item row="1" column="0">
30
+ <widget class="QLabel" name="lblLeadingSequence">
31
  <property name="text">
32
  <string>5' Leading Sequence:</string>
33
  </property>
34
  </widget>
35
  </item>
36
  <item row="1" column="1">
37
+ <widget class="QLineEdit" name="ledLeadingsequence">
38
  <property name="placeholderText">
39
  <string>E.g. promoter, tRNA, homology overhang, etc.</string>
40
  </property>
41
  </widget>
42
  </item>
43
  <item row="2" column="1">
44
+ <widget class="QLineEdit" name="ledTrailingSequence">
45
  <property name="placeholderText">
46
  <string>E.g. gRNA scaffold, terminator, etc.</string>
47
  </property>
48
  </widget>
49
  </item>
50
  <item row="2" column="0">
51
+ <widget class="QLabel" name="lblTrailingSequence">
52
  <property name="text">
53
  <string>3' Trailing Sequence:</string>
54
  </property>
 
57
  </layout>
58
  </widget>
59
  </item>
60
+ <item row="0" column="0" colspan="2">
61
+ <widget class="QGroupBox" name="grpExportOptions">
62
  <property name="title">
63
  <string>Export Options</string>
64
  </property>
65
  <layout class="QGridLayout" name="gridLayout_3">
66
  <item row="0" column="0">
67
+ <widget class="QLabel" name="lblFilePath">
68
  <property name="text">
69
+ <string>File Path:</string>
70
  </property>
71
  </widget>
72
  </item>
73
  <item row="1" column="0">
74
+ <widget class="QLabel" name="lblFileName">
75
  <property name="sizePolicy">
76
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
77
  <horstretch>0</horstretch>
 
84
  </widget>
85
  </item>
86
  <item row="1" column="2">
87
+ <widget class="QLabel" name="lblDelimiter">
88
  <property name="text">
89
  <string>Delimiter:</string>
90
  </property>
91
  </widget>
92
  </item>
93
  <item row="1" column="1">
94
+ <widget class="QLineEdit" name="ledFileName">
95
  <property name="toolTip">
96
  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Please write the file name you want.&lt;/p&gt;&lt;p&gt;Note: it is not necessary to add the file extension.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
97
  </property>
 
101
  </widget>
102
  </item>
103
  <item row="1" column="3">
104
+ <widget class="QComboBox" name="cmbDelimiter">
105
  <item>
106
  <property name="text">
107
  <string>,</string>
 
125
  </widget>
126
  </item>
127
  <item row="0" column="3">
128
+ <widget class="QPushButton" name="pbtnBrowse">
129
  <property name="sizePolicy">
130
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
131
  <horstretch>0</horstretch>
 
141
  </widget>
142
  </item>
143
  <item row="0" column="1" colspan="2">
144
+ <widget class="QLineEdit" name="ledFilePath">
145
  <property name="readOnly">
146
  <bool>true</bool>
147
  </property>
 
153
  </layout>
154
  </widget>
155
  </item>
156
+ <item row="2" column="0" alignment="Qt::AlignLeft">
157
+ <widget class="QPushButton" name="pbtnCancel">
158
  <property name="sizePolicy">
159
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
160
  <horstretch>0</horstretch>
 
168
  </size>
169
  </property>
170
  <property name="text">
171
+ <string>Cancel</string>
172
  </property>
173
  </widget>
174
  </item>
175
+ <item row="2" column="1" alignment="Qt::AlignRight">
176
+ <widget class="QPushButton" name="pbtnExport">
177
  <property name="sizePolicy">
178
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
179
  <horstretch>0</horstretch>
 
187
  </size>
188
  </property>
189
  <property name="text">
190
+ <string>Export</string>
191
  </property>
192
  </widget>
193
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  </layout>
195
  </widget>
 
196
  </widget>
197
  <resources/>
198
  <connections/>
src/ui/find_targets.ui CHANGED
@@ -80,7 +80,32 @@
80
  </property>
81
  </widget>
82
  </item>
83
- <item alignment="Qt::AlignRight">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  <widget class="QPushButton" name="pbtnViewTargets">
85
  <property name="sizePolicy">
86
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
 
80
  </property>
81
  </widget>
82
  </item>
83
+ <item>
84
+ <widget class="QPushButton" name="pbtnGenerateLibrary">
85
+ <property name="sizePolicy">
86
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
87
+ <horstretch>0</horstretch>
88
+ <verstretch>0</verstretch>
89
+ </sizepolicy>
90
+ </property>
91
+ <property name="minimumSize">
92
+ <size>
93
+ <width>0</width>
94
+ <height>0</height>
95
+ </size>
96
+ </property>
97
+ <property name="maximumSize">
98
+ <size>
99
+ <width>16777215</width>
100
+ <height>16777215</height>
101
+ </size>
102
+ </property>
103
+ <property name="text">
104
+ <string>Generate Library</string>
105
+ </property>
106
+ </widget>
107
+ </item>
108
+ <item>
109
  <widget class="QPushButton" name="pbtnViewTargets">
110
  <property name="sizePolicy">
111
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
src/ui/generate_library.ui CHANGED
@@ -16,12 +16,12 @@
16
  <widget class="QWidget" name="centralwidget">
17
  <layout class="QGridLayout" name="gridLayout_2">
18
  <item row="1" column="1">
19
- <layout class="QGridLayout" name="gridLayout" rowstretch="1,0,0,0,0" columnstretch="1,0">
20
  <property name="sizeConstraint">
21
  <enum>QLayout::SetDefaultConstraint</enum>
22
  </property>
23
- <item row="4" column="1" alignment="Qt::AlignRight">
24
- <widget class="QPushButton" name="submit_button">
25
  <property name="sizePolicy">
26
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
27
  <horstretch>0</horstretch>
@@ -39,8 +39,8 @@
39
  </property>
40
  </widget>
41
  </item>
42
- <item row="3" column="1">
43
- <widget class="QGroupBox" name="Step4">
44
  <property name="sizePolicy">
45
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
46
  <horstretch>0</horstretch>
@@ -52,7 +52,7 @@
52
  </property>
53
  <layout class="QGridLayout" name="gridLayout_7" rowstretch="0,0,0,0">
54
  <item row="2" column="2">
55
- <widget class="QPushButton" name="BrowseButton">
56
  <property name="sizePolicy">
57
  <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
58
  <horstretch>0</horstretch>
@@ -84,7 +84,7 @@
84
  </spacer>
85
  </item>
86
  <item row="2" column="0">
87
- <widget class="QLabel" name="label_14">
88
  <property name="sizePolicy">
89
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
90
  <horstretch>0</horstretch>
@@ -95,12 +95,12 @@
95
  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This is the directory that CASPER will write the library CSV to.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
96
  </property>
97
  <property name="text">
98
- <string>Output path:</string>
99
  </property>
100
  </widget>
101
  </item>
102
  <item row="1" column="0">
103
- <widget class="QLabel" name="label_13">
104
  <property name="sizePolicy">
105
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
106
  <horstretch>0</horstretch>
@@ -116,7 +116,7 @@
116
  </widget>
117
  </item>
118
  <item row="1" column="1" colspan="2">
119
- <widget class="QLineEdit" name="filename_input">
120
  <property name="sizePolicy">
121
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
122
  <horstretch>0</horstretch>
@@ -126,7 +126,7 @@
126
  </widget>
127
  </item>
128
  <item row="2" column="1">
129
- <widget class="QLineEdit" name="output_path">
130
  <property name="sizePolicy">
131
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
132
  <horstretch>0</horstretch>
@@ -154,8 +154,8 @@
154
  </layout>
155
  </widget>
156
  </item>
157
- <item row="4" column="0" alignment="Qt::AlignLeft">
158
- <widget class="QPushButton" name="cancel_button">
159
  <property name="sizePolicy">
160
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
161
  <horstretch>0</horstretch>
@@ -176,21 +176,8 @@
176
  </property>
177
  </widget>
178
  </item>
179
- <item row="1" column="0" colspan="2">
180
- <widget class="Line" name="line">
181
- <property name="sizePolicy">
182
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
183
- <horstretch>0</horstretch>
184
- <verstretch>0</verstretch>
185
- </sizepolicy>
186
- </property>
187
- <property name="orientation">
188
- <enum>Qt::Horizontal</enum>
189
- </property>
190
- </widget>
191
- </item>
192
- <item row="2" column="1">
193
- <widget class="QGroupBox" name="Step3">
194
  <property name="sizePolicy">
195
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
196
  <horstretch>0</horstretch>
@@ -205,7 +192,7 @@
205
  <enum>QLayout::SetDefaultConstraint</enum>
206
  </property>
207
  <item row="3" column="1">
208
- <widget class="QCheckBox" name="modifyParamscheckBox">
209
  <property name="sizePolicy">
210
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
211
  <horstretch>0</horstretch>
@@ -218,7 +205,7 @@
218
  </widget>
219
  </item>
220
  <item row="1" column="0">
221
- <widget class="QLabel" name="label_10">
222
  <property name="sizePolicy">
223
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
224
  <horstretch>0</horstretch>
@@ -234,7 +221,7 @@
234
  </widget>
235
  </item>
236
  <item row="2" column="1">
237
- <widget class="QLineEdit" name="fiveprimeseq">
238
  <property name="sizePolicy">
239
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
240
  <horstretch>0</horstretch>
@@ -247,7 +234,7 @@
247
  </widget>
248
  </item>
249
  <item row="1" column="1">
250
- <widget class="QLineEdit" name="space_line_edit">
251
  <property name="sizePolicy">
252
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
253
  <horstretch>0</horstretch>
@@ -276,7 +263,7 @@
276
  </spacer>
277
  </item>
278
  <item row="2" column="0">
279
- <widget class="QLabel" name="label_11">
280
  <property name="sizePolicy">
281
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
282
  <horstretch>0</horstretch>
@@ -292,7 +279,7 @@
292
  </widget>
293
  </item>
294
  <item row="3" column="0">
295
- <widget class="QLabel" name="label_12">
296
  <property name="sizePolicy">
297
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
298
  <horstretch>0</horstretch>
@@ -327,7 +314,7 @@
327
  </widget>
328
  </item>
329
  <item row="0" column="0" colspan="2">
330
- <widget class="QLabel" name="label">
331
  <property name="sizePolicy">
332
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
333
  <horstretch>0</horstretch>
@@ -339,8 +326,8 @@
339
  </property>
340
  </widget>
341
  </item>
342
- <item row="3" column="0">
343
- <widget class="QGroupBox" name="Step2">
344
  <property name="sizePolicy">
345
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
346
  <horstretch>0</horstretch>
@@ -352,7 +339,7 @@
352
  </property>
353
  <layout class="QGridLayout" name="gridLayout_9" rowstretch="0,0,0,0,0,0" columnstretch="0,0">
354
  <item row="1" column="0">
355
- <widget class="QLabel" name="label_6">
356
  <property name="sizePolicy">
357
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
358
  <horstretch>0</horstretch>
@@ -384,7 +371,7 @@
384
  </spacer>
385
  </item>
386
  <item row="4" column="1">
387
- <widget class="QProgressBar" name="progressBar">
388
  <property name="sizePolicy">
389
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
390
  <horstretch>0</horstretch>
@@ -397,7 +384,7 @@
397
  </widget>
398
  </item>
399
  <item row="1" column="1">
400
- <widget class="QCheckBox" name="find_off_Checkbox">
401
  <property name="sizePolicy">
402
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
403
  <horstretch>0</horstretch>
@@ -410,7 +397,7 @@
410
  </widget>
411
  </item>
412
  <item row="2" column="1">
413
- <widget class="QComboBox" name="minON_comboBox">
414
  <property name="sizePolicy">
415
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
416
  <horstretch>0</horstretch>
@@ -420,7 +407,7 @@
420
  </widget>
421
  </item>
422
  <item row="2" column="0">
423
- <widget class="QLabel" name="label_7">
424
  <property name="sizePolicy">
425
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
426
  <horstretch>0</horstretch>
@@ -436,7 +423,7 @@
436
  </widget>
437
  </item>
438
  <item row="3" column="1">
439
- <widget class="QLineEdit" name="maxOFF_comboBox">
440
  <property name="sizePolicy">
441
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
442
  <horstretch>0</horstretch>
@@ -449,7 +436,7 @@
449
  </widget>
450
  </item>
451
  <item row="4" column="0">
452
- <widget class="QLabel" name="label_9">
453
  <property name="sizePolicy">
454
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
455
  <horstretch>0</horstretch>
@@ -462,7 +449,7 @@
462
  </widget>
463
  </item>
464
  <item row="3" column="0">
465
- <widget class="QLabel" name="label_8">
466
  <property name="sizePolicy">
467
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
468
  <horstretch>0</horstretch>
@@ -496,8 +483,8 @@
496
  </layout>
497
  </widget>
498
  </item>
499
- <item row="2" column="0">
500
- <widget class="QGroupBox" name="Step1">
501
  <property name="sizePolicy">
502
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
503
  <horstretch>0</horstretch>
@@ -509,7 +496,7 @@
509
  </property>
510
  <layout class="QGridLayout" name="gridLayout_10">
511
  <item row="1" column="1" colspan="3">
512
- <widget class="QComboBox" name="numGenescomboBox">
513
  <property name="sizePolicy">
514
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
515
  <horstretch>0</horstretch>
@@ -519,7 +506,7 @@
519
  </widget>
520
  </item>
521
  <item row="2" column="0">
522
- <widget class="QLabel" name="label_3">
523
  <property name="sizePolicy">
524
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
525
  <horstretch>0</horstretch>
@@ -535,7 +522,7 @@
535
  </widget>
536
  </item>
537
  <item row="1" column="0">
538
- <widget class="QLabel" name="label_2">
539
  <property name="sizePolicy">
540
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
541
  <horstretch>0</horstretch>
@@ -551,7 +538,7 @@
551
  </widget>
552
  </item>
553
  <item row="3" column="0" colspan="4">
554
- <widget class="QLabel" name="label_4">
555
  <property name="sizePolicy">
556
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
557
  <horstretch>0</horstretch>
@@ -564,7 +551,7 @@
564
  </widget>
565
  </item>
566
  <item row="2" column="1">
567
- <widget class="QLineEdit" name="start_target_range">
568
  <property name="sizePolicy">
569
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
570
  <horstretch>0</horstretch>
@@ -577,7 +564,7 @@
577
  </widget>
578
  </item>
579
  <item row="2" column="2">
580
- <widget class="QLabel" name="label_5">
581
  <property name="sizePolicy">
582
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
583
  <horstretch>0</horstretch>
@@ -593,7 +580,7 @@
593
  </widget>
594
  </item>
595
  <item row="2" column="3">
596
- <widget class="QLineEdit" name="end_target_range">
597
  <property name="sizePolicy">
598
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
599
  <horstretch>0</horstretch>
@@ -708,23 +695,22 @@
708
  </item>
709
  </layout>
710
  </widget>
711
- <widget class="QStatusBar" name="statusbar"/>
712
  </widget>
713
  <tabstops>
714
- <tabstop>numGenescomboBox</tabstop>
715
- <tabstop>start_target_range</tabstop>
716
- <tabstop>end_target_range</tabstop>
717
- <tabstop>find_off_Checkbox</tabstop>
718
- <tabstop>minON_comboBox</tabstop>
719
- <tabstop>maxOFF_comboBox</tabstop>
720
- <tabstop>space_line_edit</tabstop>
721
- <tabstop>fiveprimeseq</tabstop>
722
- <tabstop>modifyParamscheckBox</tabstop>
723
- <tabstop>filename_input</tabstop>
724
- <tabstop>output_path</tabstop>
725
- <tabstop>BrowseButton</tabstop>
726
- <tabstop>cancel_button</tabstop>
727
- <tabstop>submit_button</tabstop>
728
  </tabstops>
729
  <resources/>
730
  <connections/>
 
16
  <widget class="QWidget" name="centralwidget">
17
  <layout class="QGridLayout" name="gridLayout_2">
18
  <item row="1" column="1">
19
+ <layout class="QGridLayout" name="gridLayout" rowstretch="1,0,0,0" columnstretch="1,0">
20
  <property name="sizeConstraint">
21
  <enum>QLayout::SetDefaultConstraint</enum>
22
  </property>
23
+ <item row="3" column="1" alignment="Qt::AlignRight">
24
+ <widget class="QPushButton" name="pbtnSubmit">
25
  <property name="sizePolicy">
26
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
27
  <horstretch>0</horstretch>
 
39
  </property>
40
  </widget>
41
  </item>
42
+ <item row="2" column="1">
43
+ <widget class="QGroupBox" name="grpOutputLocation">
44
  <property name="sizePolicy">
45
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
46
  <horstretch>0</horstretch>
 
52
  </property>
53
  <layout class="QGridLayout" name="gridLayout_7" rowstretch="0,0,0,0">
54
  <item row="2" column="2">
55
+ <widget class="QPushButton" name="pbtnBrowse">
56
  <property name="sizePolicy">
57
  <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
58
  <horstretch>0</horstretch>
 
84
  </spacer>
85
  </item>
86
  <item row="2" column="0">
87
+ <widget class="QLabel" name="lblFilePath">
88
  <property name="sizePolicy">
89
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
90
  <horstretch>0</horstretch>
 
95
  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This is the directory that CASPER will write the library CSV to.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
96
  </property>
97
  <property name="text">
98
+ <string>Output file path:</string>
99
  </property>
100
  </widget>
101
  </item>
102
  <item row="1" column="0">
103
+ <widget class="QLabel" name="lblFileName">
104
  <property name="sizePolicy">
105
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
106
  <horstretch>0</horstretch>
 
116
  </widget>
117
  </item>
118
  <item row="1" column="1" colspan="2">
119
+ <widget class="QLineEdit" name="ledFileName">
120
  <property name="sizePolicy">
121
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
122
  <horstretch>0</horstretch>
 
126
  </widget>
127
  </item>
128
  <item row="2" column="1">
129
+ <widget class="QLineEdit" name="ledFilePath">
130
  <property name="sizePolicy">
131
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
132
  <horstretch>0</horstretch>
 
154
  </layout>
155
  </widget>
156
  </item>
157
+ <item row="3" column="0" alignment="Qt::AlignLeft">
158
+ <widget class="QPushButton" name="pbtnCancel">
159
  <property name="sizePolicy">
160
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
161
  <horstretch>0</horstretch>
 
176
  </property>
177
  </widget>
178
  </item>
179
+ <item row="1" column="1">
180
+ <widget class="QGroupBox" name="grpTargetingGuidelines">
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  <property name="sizePolicy">
182
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
183
  <horstretch>0</horstretch>
 
192
  <enum>QLayout::SetDefaultConstraint</enum>
193
  </property>
194
  <item row="3" column="1">
195
+ <widget class="QCheckBox" name="chkModifyParameters">
196
  <property name="sizePolicy">
197
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
198
  <horstretch>0</horstretch>
 
205
  </widget>
206
  </item>
207
  <item row="1" column="0">
208
+ <widget class="QLabel" name="lblSpaceBetweenGuides">
209
  <property name="sizePolicy">
210
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
211
  <horstretch>0</horstretch>
 
221
  </widget>
222
  </item>
223
  <item row="2" column="1">
224
+ <widget class="QLineEdit" name="led5PrimeSpecificity">
225
  <property name="sizePolicy">
226
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
227
  <horstretch>0</horstretch>
 
234
  </widget>
235
  </item>
236
  <item row="1" column="1">
237
+ <widget class="QLineEdit" name="ledSpaceBetweenGuides">
238
  <property name="sizePolicy">
239
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
240
  <horstretch>0</horstretch>
 
263
  </spacer>
264
  </item>
265
  <item row="2" column="0">
266
+ <widget class="QLabel" name="lbl5PrimeSpecificity">
267
  <property name="sizePolicy">
268
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
269
  <horstretch>0</horstretch>
 
279
  </widget>
280
  </item>
281
  <item row="3" column="0">
282
+ <widget class="QLabel" name="lblModifyParameters">
283
  <property name="sizePolicy">
284
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
285
  <horstretch>0</horstretch>
 
314
  </widget>
315
  </item>
316
  <item row="0" column="0" colspan="2">
317
+ <widget class="QLabel" name="lblTitle">
318
  <property name="sizePolicy">
319
  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
320
  <horstretch>0</horstretch>
 
326
  </property>
327
  </widget>
328
  </item>
329
+ <item row="2" column="0">
330
+ <widget class="QGroupBox" name="grpQualityFiltering">
331
  <property name="sizePolicy">
332
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
333
  <horstretch>0</horstretch>
 
339
  </property>
340
  <layout class="QGridLayout" name="gridLayout_9" rowstretch="0,0,0,0,0,0" columnstretch="0,0">
341
  <item row="1" column="0">
342
+ <widget class="QLabel" name="lblFindOffTargets">
343
  <property name="sizePolicy">
344
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
345
  <horstretch>0</horstretch>
 
371
  </spacer>
372
  </item>
373
  <item row="4" column="1">
374
+ <widget class="QProgressBar" name="progBar">
375
  <property name="sizePolicy">
376
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
377
  <horstretch>0</horstretch>
 
384
  </widget>
385
  </item>
386
  <item row="1" column="1">
387
+ <widget class="QCheckBox" name="chkFindOffTargets">
388
  <property name="sizePolicy">
389
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
390
  <horstretch>0</horstretch>
 
397
  </widget>
398
  </item>
399
  <item row="2" column="1">
400
+ <widget class="QComboBox" name="cmbMinimumOnTargetScore">
401
  <property name="sizePolicy">
402
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
403
  <horstretch>0</horstretch>
 
407
  </widget>
408
  </item>
409
  <item row="2" column="0">
410
+ <widget class="QLabel" name="lblMinimumOnTargetScore">
411
  <property name="sizePolicy">
412
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
413
  <horstretch>0</horstretch>
 
423
  </widget>
424
  </item>
425
  <item row="3" column="1">
426
+ <widget class="QLineEdit" name="cmbMaximumOffTargetScore">
427
  <property name="sizePolicy">
428
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
429
  <horstretch>0</horstretch>
 
436
  </widget>
437
  </item>
438
  <item row="4" column="0">
439
+ <widget class="QLabel" name="lblOffTargetProgress">
440
  <property name="sizePolicy">
441
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
442
  <horstretch>0</horstretch>
 
449
  </widget>
450
  </item>
451
  <item row="3" column="0">
452
+ <widget class="QLabel" name="lblMaximumOffTargetScore">
453
  <property name="sizePolicy">
454
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
455
  <horstretch>0</horstretch>
 
483
  </layout>
484
  </widget>
485
  </item>
486
+ <item row="1" column="0">
487
+ <widget class="QGroupBox" name="grpLibrarySizeAndLocation">
488
  <property name="sizePolicy">
489
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
490
  <horstretch>0</horstretch>
 
496
  </property>
497
  <layout class="QGridLayout" name="gridLayout_10">
498
  <item row="1" column="1" colspan="3">
499
+ <widget class="QComboBox" name="cmbGuidesPerGene">
500
  <property name="sizePolicy">
501
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
502
  <horstretch>0</horstretch>
 
506
  </widget>
507
  </item>
508
  <item row="2" column="0">
509
+ <widget class="QLabel" name="lblTargetingRange">
510
  <property name="sizePolicy">
511
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
512
  <horstretch>0</horstretch>
 
522
  </widget>
523
  </item>
524
  <item row="1" column="0">
525
+ <widget class="QLabel" name="lblGuidesPerGene">
526
  <property name="sizePolicy">
527
  <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
528
  <horstretch>0</horstretch>
 
538
  </widget>
539
  </item>
540
  <item row="3" column="0" colspan="4">
541
+ <widget class="QLabel" name="lblStartEndDescription">
542
  <property name="sizePolicy">
543
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
544
  <horstretch>0</horstretch>
 
551
  </widget>
552
  </item>
553
  <item row="2" column="1">
554
+ <widget class="QLineEdit" name="ledTargetRangeStart">
555
  <property name="sizePolicy">
556
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
557
  <horstretch>0</horstretch>
 
564
  </widget>
565
  </item>
566
  <item row="2" column="2">
567
+ <widget class="QLabel" name="lblTo">
568
  <property name="sizePolicy">
569
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
570
  <horstretch>0</horstretch>
 
580
  </widget>
581
  </item>
582
  <item row="2" column="3">
583
+ <widget class="QLineEdit" name="ledTargetRangeEnd">
584
  <property name="sizePolicy">
585
  <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
586
  <horstretch>0</horstretch>
 
695
  </item>
696
  </layout>
697
  </widget>
 
698
  </widget>
699
  <tabstops>
700
+ <tabstop>cmbGuidesPerGene</tabstop>
701
+ <tabstop>ledTargetRangeStart</tabstop>
702
+ <tabstop>ledTargetRangeEnd</tabstop>
703
+ <tabstop>chkFindOffTargets</tabstop>
704
+ <tabstop>cmbMinimumOnTargetScore</tabstop>
705
+ <tabstop>cmbMaximumOffTargetScore</tabstop>
706
+ <tabstop>ledSpaceBetweenGuides</tabstop>
707
+ <tabstop>led5PrimeSpecificity</tabstop>
708
+ <tabstop>chkModifyParameters</tabstop>
709
+ <tabstop>ledFileName</tabstop>
710
+ <tabstop>ledFilePath</tabstop>
711
+ <tabstop>pbtnBrowse</tabstop>
712
+ <tabstop>pbtnCancel</tabstop>
713
+ <tabstop>pbtnSubmit</tabstop>
714
  </tabstops>
715
  <resources/>
716
  <connections/>
src/ui/home_window.ui CHANGED
@@ -1,7 +1,7 @@
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <ui version="4.0">
3
  <class>MainWindow</class>
4
- <widget class="QMainWindow" name="MainWindow">
5
  <property name="enabled">
6
  <bool>true</bool>
7
  </property>
@@ -9,8 +9,8 @@
9
  <rect>
10
  <x>0</x>
11
  <y>0</y>
12
- <width>672</width>
13
- <height>788</height>
14
  </rect>
15
  </property>
16
  <property name="font">
@@ -29,6 +29,14 @@
29
  <string notr="true"/>
30
  </property>
31
  <widget class="QWidget" name="centralwidget">
 
 
 
 
 
 
 
 
32
  <property name="font">
33
  <font>
34
  <family>Arial</family>
@@ -41,7 +49,7 @@
41
  <property name="styleSheet">
42
  <string notr="true"/>
43
  </property>
44
- <layout class="QGridLayout" name="gridLayout_6">
45
  <item row="4" column="0" rowspan="2" colspan="2">
46
  <layout class="QGridLayout" name="gridStep1Step2Step3">
47
  <property name="sizeConstraint">
@@ -103,15 +111,6 @@
103
  <height>16777215</height>
104
  </size>
105
  </property>
106
- <property name="font">
107
- <font>
108
- <family>Arial</family>
109
- <pointsize>8</pointsize>
110
- <weight>50</weight>
111
- <italic>false</italic>
112
- <bold>false</bold>
113
- </font>
114
- </property>
115
  </widget>
116
  </item>
117
  <item row="1" column="1">
@@ -137,15 +136,6 @@
137
  <height>16777215</height>
138
  </size>
139
  </property>
140
- <property name="font">
141
- <font>
142
- <family>Arial</family>
143
- <pointsize>8</pointsize>
144
- <weight>50</weight>
145
- <italic>false</italic>
146
- <bold>false</bold>
147
- </font>
148
- </property>
149
  </widget>
150
  </item>
151
  <item row="1" column="0">
@@ -442,33 +432,14 @@
442
  <property name="topMargin">
443
  <number>15</number>
444
  </property>
445
- <item row="0" column="2">
446
- <widget class="QRadioButton" name="rbtnSequence">
447
- <property name="text">
448
- <string>Sequence</string>
449
- </property>
450
- </widget>
451
- </item>
452
- <item row="1" column="0" colspan="3">
453
- <widget class="QPlainTextEdit" name="txtedGeneEntry">
454
  <property name="sizePolicy">
455
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
456
  <horstretch>0</horstretch>
457
  <verstretch>0</verstretch>
458
  </sizepolicy>
459
  </property>
460
- <property name="minimumSize">
461
- <size>
462
- <width>250</width>
463
- <height>0</height>
464
- </size>
465
- </property>
466
- <property name="maximumSize">
467
- <size>
468
- <width>16777215</width>
469
- <height>16777215</height>
470
- </size>
471
- </property>
472
  <property name="font">
473
  <font>
474
  <family>Arial</family>
@@ -478,41 +449,32 @@
478
  <bold>false</bold>
479
  </font>
480
  </property>
481
- </widget>
482
- </item>
483
- <item row="4" column="0" colspan="3">
484
- <widget class="QProgressBar" name="progBarFindTargets">
485
- <property name="sizePolicy">
486
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
487
- <horstretch>0</horstretch>
488
- <verstretch>0</verstretch>
489
- </sizepolicy>
490
- </property>
491
- <property name="minimumSize">
492
- <size>
493
- <width>0</width>
494
- <height>0</height>
495
- </size>
496
  </property>
497
- <property name="value">
498
- <number>24</number>
499
  </property>
500
  </widget>
501
  </item>
502
- <item row="2" column="0" colspan="3">
503
- <widget class="QPushButton" name="pbtnFindTargets">
504
- <property name="enabled">
505
- <bool>true</bool>
506
  </property>
 
 
 
 
507
  <property name="sizePolicy">
508
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
509
  <horstretch>0</horstretch>
510
  <verstretch>0</verstretch>
511
  </sizepolicy>
512
  </property>
513
  <property name="minimumSize">
514
  <size>
515
- <width>0</width>
516
  <height>0</height>
517
  </size>
518
  </property>
@@ -522,19 +484,6 @@
522
  <height>16777215</height>
523
  </size>
524
  </property>
525
- <property name="text">
526
- <string>Find Targets</string>
527
- </property>
528
- </widget>
529
- </item>
530
- <item row="0" column="0">
531
- <widget class="QRadioButton" name="rbtnFeature">
532
- <property name="sizePolicy">
533
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
534
- <horstretch>0</horstretch>
535
- <verstretch>0</verstretch>
536
- </sizepolicy>
537
- </property>
538
  <property name="font">
539
  <font>
540
  <family>Arial</family>
@@ -544,12 +493,6 @@
544
  <bold>false</bold>
545
  </font>
546
  </property>
547
- <property name="text">
548
- <string>Feature</string>
549
- </property>
550
- <property name="checked">
551
- <bool>true</bool>
552
- </property>
553
  </widget>
554
  </item>
555
  <item row="0" column="1">
@@ -580,10 +523,10 @@
580
  </property>
581
  </widget>
582
  </item>
583
- <item row="5" column="0" colspan="3">
584
  <layout class="QHBoxLayout" name="horizontalLayout">
585
  <item>
586
- <widget class="QPushButton" name="pbtnViewTargets">
587
  <property name="sizePolicy">
588
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
589
  <horstretch>0</horstretch>
@@ -597,32 +540,7 @@
597
  </size>
598
  </property>
599
  <property name="text">
600
- <string>View Targets</string>
601
- </property>
602
- </widget>
603
- </item>
604
- <item>
605
- <widget class="QPushButton" name="pbtnGenerateLibrary">
606
- <property name="sizePolicy">
607
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
608
- <horstretch>0</horstretch>
609
- <verstretch>0</verstretch>
610
- </sizepolicy>
611
- </property>
612
- <property name="minimumSize">
613
- <size>
614
- <width>0</width>
615
- <height>0</height>
616
- </size>
617
- </property>
618
- <property name="maximumSize">
619
- <size>
620
- <width>16777215</width>
621
- <height>16777215</height>
622
- </size>
623
- </property>
624
- <property name="text">
625
- <string>Generate Library</string>
626
  </property>
627
  </widget>
628
  </item>
@@ -741,69 +659,6 @@
741
  </item>
742
  </layout>
743
  </widget>
744
- <widget class="QMenuBar" name="menuBar">
745
- <property name="geometry">
746
- <rect>
747
- <x>0</x>
748
- <y>0</y>
749
- <width>672</width>
750
- <height>24</height>
751
- </rect>
752
- </property>
753
- <property name="font">
754
- <font>
755
- <family>Arial</family>
756
- <pointsize>8</pointsize>
757
- <weight>50</weight>
758
- <italic>false</italic>
759
- <bold>false</bold>
760
- </font>
761
- </property>
762
- <widget class="QMenu" name="menuFile">
763
- <property name="title">
764
- <string>File</string>
765
- </property>
766
- <addaction name="actionExit"/>
767
- <addaction name="actionTest"/>
768
- </widget>
769
- <widget class="QMenu" name="menuGenome">
770
- <property name="title">
771
- <string>Genome</string>
772
- </property>
773
- <addaction name="actChangeDirectory"/>
774
- <addaction name="actOpenGenomeBrowser"/>
775
- </widget>
776
- <widget class="QMenu" name="menuTools">
777
- <property name="title">
778
- <string>Tools</string>
779
- </property>
780
- <addaction name="actOpenNCBIBLAST"/>
781
- </widget>
782
- <widget class="QMenu" name="menuWindow">
783
- <property name="title">
784
- <string>Web Links</string>
785
- </property>
786
- <addaction name="actOpenNCBI"/>
787
- </widget>
788
- <widget class="QMenu" name="menuHelp">
789
- <property name="title">
790
- <string>Help</string>
791
- </property>
792
- <addaction name="actOpenRepository"/>
793
- </widget>
794
- <widget class="QMenu" name="menuCASPER">
795
- <property name="title">
796
- <string>CASPER</string>
797
- </property>
798
- <addaction name="actionAbout_CASPER"/>
799
- </widget>
800
- <addaction name="menuCASPER"/>
801
- <addaction name="menuFile"/>
802
- <addaction name="menuGenome"/>
803
- <addaction name="menuTools"/>
804
- <addaction name="menuWindow"/>
805
- <addaction name="menuHelp"/>
806
- </widget>
807
  <action name="actionNew">
808
  <property name="text">
809
  <string>New</string>
@@ -1011,7 +866,6 @@
1011
  <tabstop>rbtnFeature</tabstop>
1012
  <tabstop>rbtnPosition</tabstop>
1013
  <tabstop>txtedGeneEntry</tabstop>
1014
- <tabstop>pbtnFindTargets</tabstop>
1015
  </tabstops>
1016
  <resources/>
1017
  <connections/>
 
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <ui version="4.0">
3
  <class>MainWindow</class>
4
+ <widget class="QWidget" name="MainWindow">
5
  <property name="enabled">
6
  <bool>true</bool>
7
  </property>
 
9
  <rect>
10
  <x>0</x>
11
  <y>0</y>
12
+ <width>960</width>
13
+ <height>1147</height>
14
  </rect>
15
  </property>
16
  <property name="font">
 
29
  <string notr="true"/>
30
  </property>
31
  <widget class="QWidget" name="centralwidget">
32
+ <property name="geometry">
33
+ <rect>
34
+ <x>0</x>
35
+ <y>0</y>
36
+ <width>560</width>
37
+ <height>522</height>
38
+ </rect>
39
+ </property>
40
  <property name="font">
41
  <font>
42
  <family>Arial</family>
 
49
  <property name="styleSheet">
50
  <string notr="true"/>
51
  </property>
52
+ <layout class="QGridLayout" name="gridContainer">
53
  <item row="4" column="0" rowspan="2" colspan="2">
54
  <layout class="QGridLayout" name="gridStep1Step2Step3">
55
  <property name="sizeConstraint">
 
111
  <height>16777215</height>
112
  </size>
113
  </property>
 
 
 
 
 
 
 
 
 
114
  </widget>
115
  </item>
116
  <item row="1" column="1">
 
136
  <height>16777215</height>
137
  </size>
138
  </property>
 
 
 
 
 
 
 
 
 
139
  </widget>
140
  </item>
141
  <item row="1" column="0">
 
432
  <property name="topMargin">
433
  <number>15</number>
434
  </property>
435
+ <item row="0" column="0">
436
+ <widget class="QRadioButton" name="rbtnFeature">
 
 
 
 
 
 
 
437
  <property name="sizePolicy">
438
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
439
  <horstretch>0</horstretch>
440
  <verstretch>0</verstretch>
441
  </sizepolicy>
442
  </property>
 
 
 
 
 
 
 
 
 
 
 
 
443
  <property name="font">
444
  <font>
445
  <family>Arial</family>
 
449
  <bold>false</bold>
450
  </font>
451
  </property>
452
+ <property name="text">
453
+ <string>Feature</string>
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  </property>
455
+ <property name="checked">
456
+ <bool>true</bool>
457
  </property>
458
  </widget>
459
  </item>
460
+ <item row="0" column="2">
461
+ <widget class="QRadioButton" name="rbtnSequence">
462
+ <property name="text">
463
+ <string>Sequence</string>
464
  </property>
465
+ </widget>
466
+ </item>
467
+ <item row="1" column="0" colspan="3">
468
+ <widget class="QPlainTextEdit" name="txtedGeneEntry">
469
  <property name="sizePolicy">
470
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
471
  <horstretch>0</horstretch>
472
  <verstretch>0</verstretch>
473
  </sizepolicy>
474
  </property>
475
  <property name="minimumSize">
476
  <size>
477
+ <width>250</width>
478
  <height>0</height>
479
  </size>
480
  </property>
 
484
  <height>16777215</height>
485
  </size>
486
  </property>
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  <property name="font">
488
  <font>
489
  <family>Arial</family>
 
493
  <bold>false</bold>
494
  </font>
495
  </property>
 
 
 
 
 
 
496
  </widget>
497
  </item>
498
  <item row="0" column="1">
 
523
  </property>
524
  </widget>
525
  </item>
526
+ <item row="3" column="0" colspan="3">
527
  <layout class="QHBoxLayout" name="horizontalLayout">
528
  <item>
529
+ <widget class="QPushButton" name="pbtnFindViewTargets">
530
  <property name="sizePolicy">
531
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
532
  <horstretch>0</horstretch>
 
540
  </size>
541
  </property>
542
  <property name="text">
543
+ <string>Find Targets</string>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  </property>
545
  </widget>
546
  </item>
 
659
  </item>
660
  </layout>
661
  </widget>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  <action name="actionNew">
663
  <property name="text">
664
  <string>New</string>
 
866
  <tabstop>rbtnFeature</tabstop>
867
  <tabstop>rbtnPosition</tabstop>
868
  <tabstop>txtedGeneEntry</tabstop>
 
869
  </tabstops>
870
  <resources/>
871
  <connections/>
src/ui/home_window_copy.ui DELETED
@@ -1,946 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <ui version="4.0">
3
- <class>MainWindow</class>
4
- <widget class="QWidget" name="MainWindow">
5
- <property name="enabled">
6
- <bool>true</bool>
7
- </property>
8
- <property name="geometry">
9
- <rect>
10
- <x>0</x>
11
- <y>0</y>
12
- <width>672</width>
13
- <height>788</height>
14
- </rect>
15
- </property>
16
- <property name="font">
17
- <font>
18
- <family>Arial</family>
19
- <pointsize>12</pointsize>
20
- <italic>false</italic>
21
- <bold>false</bold>
22
- </font>
23
- </property>
24
- <property name="windowTitle">
25
- <string>CASPER</string>
26
- </property>
27
- <property name="styleSheet">
28
- <string notr="true"/>
29
- </property>
30
- <widget class="QWidget" name="centralwidget">
31
- <property name="geometry">
32
- <rect>
33
- <x>0</x>
34
- <y>0</y>
35
- <width>552</width>
36
- <height>522</height>
37
- </rect>
38
- </property>
39
- <property name="font">
40
- <font>
41
- <family>Arial</family>
42
- <pointsize>12</pointsize>
43
- <italic>false</italic>
44
- <bold>false</bold>
45
- </font>
46
- </property>
47
- <property name="styleSheet">
48
- <string notr="true"/>
49
- </property>
50
- <layout class="QGridLayout" name="gridContainer">
51
- <item row="4" column="0" rowspan="2" colspan="2">
52
- <layout class="QGridLayout" name="gridStep1Step2Step3">
53
- <property name="sizeConstraint">
54
- <enum>QLayout::SetDefaultConstraint</enum>
55
- </property>
56
- <item row="0" column="0" rowspan="2">
57
- <widget class="QGroupBox" name="grpStep1">
58
- <property name="sizePolicy">
59
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
60
- <horstretch>0</horstretch>
61
- <verstretch>0</verstretch>
62
- </sizepolicy>
63
- </property>
64
- <property name="minimumSize">
65
- <size>
66
- <width>0</width>
67
- <height>0</height>
68
- </size>
69
- </property>
70
- <property name="maximumSize">
71
- <size>
72
- <width>16777215</width>
73
- <height>16777215</height>
74
- </size>
75
- </property>
76
- <property name="font">
77
- <font>
78
- <family>Arial</family>
79
- <pointsize>12</pointsize>
80
- <italic>false</italic>
81
- <bold>false</bold>
82
- </font>
83
- </property>
84
- <property name="styleSheet">
85
- <string notr="true"/>
86
- </property>
87
- <property name="title">
88
- <string>Step 1: Select Organism and Endonuclease</string>
89
- </property>
90
- <layout class="QGridLayout" name="gridLayout">
91
- <item row="2" column="1">
92
- <widget class="QComboBox" name="cmbEndonuclease">
93
- <property name="sizePolicy">
94
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
95
- <horstretch>0</horstretch>
96
- <verstretch>0</verstretch>
97
- </sizepolicy>
98
- </property>
99
- <property name="minimumSize">
100
- <size>
101
- <width>0</width>
102
- <height>0</height>
103
- </size>
104
- </property>
105
- <property name="maximumSize">
106
- <size>
107
- <width>16777215</width>
108
- <height>16777215</height>
109
- </size>
110
- </property>
111
- <property name="font">
112
- <font>
113
- <family>Arial</family>
114
- <pointsize>8</pointsize>
115
- <italic>false</italic>
116
- <bold>false</bold>
117
- </font>
118
- </property>
119
- </widget>
120
- </item>
121
- <item row="1" column="1">
122
- <widget class="QComboBox" name="cmbOrganism">
123
- <property name="enabled">
124
- <bool>true</bool>
125
- </property>
126
- <property name="sizePolicy">
127
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
128
- <horstretch>0</horstretch>
129
- <verstretch>0</verstretch>
130
- </sizepolicy>
131
- </property>
132
- <property name="minimumSize">
133
- <size>
134
- <width>0</width>
135
- <height>0</height>
136
- </size>
137
- </property>
138
- <property name="maximumSize">
139
- <size>
140
- <width>16777215</width>
141
- <height>16777215</height>
142
- </size>
143
- </property>
144
- <property name="font">
145
- <font>
146
- <family>Arial</family>
147
- <pointsize>8</pointsize>
148
- <italic>false</italic>
149
- <bold>false</bold>
150
- </font>
151
- </property>
152
- </widget>
153
- </item>
154
- <item row="1" column="0">
155
- <widget class="QLabel" name="txtOrganismName">
156
- <property name="sizePolicy">
157
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
158
- <horstretch>0</horstretch>
159
- <verstretch>0</verstretch>
160
- </sizepolicy>
161
- </property>
162
- <property name="minimumSize">
163
- <size>
164
- <width>0</width>
165
- <height>0</height>
166
- </size>
167
- </property>
168
- <property name="maximumSize">
169
- <size>
170
- <width>16777215</width>
171
- <height>16777215</height>
172
- </size>
173
- </property>
174
- <property name="font">
175
- <font>
176
- <family>Arial</family>
177
- <pointsize>12</pointsize>
178
- <italic>false</italic>
179
- <bold>false</bold>
180
- </font>
181
- </property>
182
- <property name="styleSheet">
183
- <string notr="true"/>
184
- </property>
185
- <property name="text">
186
- <string>Organism Name:</string>
187
- </property>
188
- </widget>
189
- </item>
190
- <item row="2" column="0">
191
- <widget class="QLabel" name="txtEndonuclease">
192
- <property name="sizePolicy">
193
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
194
- <horstretch>0</horstretch>
195
- <verstretch>0</verstretch>
196
- </sizepolicy>
197
- </property>
198
- <property name="minimumSize">
199
- <size>
200
- <width>0</width>
201
- <height>0</height>
202
- </size>
203
- </property>
204
- <property name="maximumSize">
205
- <size>
206
- <width>16777215</width>
207
- <height>16777215</height>
208
- </size>
209
- </property>
210
- <property name="font">
211
- <font>
212
- <family>Arial</family>
213
- <pointsize>12</pointsize>
214
- <italic>false</italic>
215
- <bold>false</bold>
216
- </font>
217
- </property>
218
- <property name="styleSheet">
219
- <string notr="true"/>
220
- </property>
221
- <property name="text">
222
- <string>Endonuclease:</string>
223
- </property>
224
- </widget>
225
- </item>
226
- </layout>
227
- </widget>
228
- </item>
229
- <item row="2" column="0" rowspan="2">
230
- <widget class="QGroupBox" name="grpStep2">
231
- <property name="sizePolicy">
232
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
233
- <horstretch>0</horstretch>
234
- <verstretch>0</verstretch>
235
- </sizepolicy>
236
- </property>
237
- <property name="minimumSize">
238
- <size>
239
- <width>0</width>
240
- <height>0</height>
241
- </size>
242
- </property>
243
- <property name="maximumSize">
244
- <size>
245
- <width>16777215</width>
246
- <height>16777215</height>
247
- </size>
248
- </property>
249
- <property name="font">
250
- <font>
251
- <family>Arial</family>
252
- <pointsize>12</pointsize>
253
- <italic>false</italic>
254
- <bold>false</bold>
255
- </font>
256
- </property>
257
- <property name="styleSheet">
258
- <string notr="true"/>
259
- </property>
260
- <property name="title">
261
- <string>Step 2: Choose Annotation File</string>
262
- </property>
263
- <layout class="QGridLayout" name="gridLayout_5">
264
- <property name="topMargin">
265
- <number>15</number>
266
- </property>
267
- <item row="1" column="1" colspan="2">
268
- <widget class="QLabel" name="txtNCBIFileSearch">
269
- <property name="sizePolicy">
270
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
271
- <horstretch>0</horstretch>
272
- <verstretch>0</verstretch>
273
- </sizepolicy>
274
- </property>
275
- <property name="maximumSize">
276
- <size>
277
- <width>16777215</width>
278
- <height>16777215</height>
279
- </size>
280
- </property>
281
- <property name="font">
282
- <font>
283
- <family>Arial</family>
284
- <pointsize>12</pointsize>
285
- <italic>false</italic>
286
- <bold>false</bold>
287
- </font>
288
- </property>
289
- <property name="styleSheet">
290
- <string notr="true"/>
291
- </property>
292
- <property name="text">
293
- <string>Select local file or download from NCBI:</string>
294
- </property>
295
- <property name="alignment">
296
- <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
297
- </property>
298
- </widget>
299
- </item>
300
- <item row="3" column="1" colspan="2">
301
- <widget class="QLabel" name="txtLocalAnnotationFiles">
302
- <property name="sizePolicy">
303
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
304
- <horstretch>0</horstretch>
305
- <verstretch>0</verstretch>
306
- </sizepolicy>
307
- </property>
308
- <property name="minimumSize">
309
- <size>
310
- <width>0</width>
311
- <height>0</height>
312
- </size>
313
- </property>
314
- <property name="maximumSize">
315
- <size>
316
- <width>16777215</width>
317
- <height>16777215</height>
318
- </size>
319
- </property>
320
- <property name="font">
321
- <font>
322
- <family>Arial</family>
323
- <pointsize>12</pointsize>
324
- <italic>false</italic>
325
- <bold>false</bold>
326
- </font>
327
- </property>
328
- <property name="text">
329
- <string>Local Annotation Files:</string>
330
- </property>
331
- <property name="alignment">
332
- <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
333
- </property>
334
- </widget>
335
- </item>
336
- <item row="4" column="1" colspan="2">
337
- <widget class="QComboBox" name="cmbLocalAnnotationFiles">
338
- <property name="sizePolicy">
339
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
340
- <horstretch>0</horstretch>
341
- <verstretch>0</verstretch>
342
- </sizepolicy>
343
- </property>
344
- <property name="minimumSize">
345
- <size>
346
- <width>0</width>
347
- <height>0</height>
348
- </size>
349
- </property>
350
- <property name="maximumSize">
351
- <size>
352
- <width>16777215</width>
353
- <height>16777215</height>
354
- </size>
355
- </property>
356
- <property name="font">
357
- <font>
358
- <family>Arial</family>
359
- <pointsize>12</pointsize>
360
- <italic>false</italic>
361
- <bold>false</bold>
362
- </font>
363
- </property>
364
- </widget>
365
- </item>
366
- <item row="2" column="1" colspan="2">
367
- <widget class="QPushButton" name="pbtnNCBIFileSearch">
368
- <property name="sizePolicy">
369
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
370
- <horstretch>0</horstretch>
371
- <verstretch>0</verstretch>
372
- </sizepolicy>
373
- </property>
374
- <property name="minimumSize">
375
- <size>
376
- <width>125</width>
377
- <height>0</height>
378
- </size>
379
- </property>
380
- <property name="maximumSize">
381
- <size>
382
- <width>16777215</width>
383
- <height>16777215</height>
384
- </size>
385
- </property>
386
- <property name="font">
387
- <font>
388
- <family>Arial</family>
389
- <pointsize>12</pointsize>
390
- <italic>false</italic>
391
- <bold>false</bold>
392
- </font>
393
- </property>
394
- <property name="text">
395
- <string>NCBI File Search</string>
396
- </property>
397
- </widget>
398
- </item>
399
- </layout>
400
- </widget>
401
- </item>
402
- <item row="0" column="1" rowspan="4">
403
- <widget class="QGroupBox" name="grpStep3">
404
- <property name="sizePolicy">
405
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
406
- <horstretch>0</horstretch>
407
- <verstretch>0</verstretch>
408
- </sizepolicy>
409
- </property>
410
- <property name="minimumSize">
411
- <size>
412
- <width>0</width>
413
- <height>0</height>
414
- </size>
415
- </property>
416
- <property name="maximumSize">
417
- <size>
418
- <width>16777215</width>
419
- <height>16777215</height>
420
- </size>
421
- </property>
422
- <property name="font">
423
- <font>
424
- <family>Arial</family>
425
- <pointsize>12</pointsize>
426
- <italic>false</italic>
427
- <bold>false</bold>
428
- </font>
429
- </property>
430
- <property name="styleSheet">
431
- <string notr="true"/>
432
- </property>
433
- <property name="title">
434
- <string>Step 3: Search and Find Targets</string>
435
- </property>
436
- <layout class="QGridLayout" name="gridLayout_4">
437
- <property name="topMargin">
438
- <number>15</number>
439
- </property>
440
- <item row="0" column="2">
441
- <widget class="QRadioButton" name="rbtnSequence">
442
- <property name="text">
443
- <string>Sequence</string>
444
- </property>
445
- </widget>
446
- </item>
447
- <item row="1" column="0" colspan="3">
448
- <widget class="QPlainTextEdit" name="txtedGeneEntry">
449
- <property name="sizePolicy">
450
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
451
- <horstretch>0</horstretch>
452
- <verstretch>0</verstretch>
453
- </sizepolicy>
454
- </property>
455
- <property name="minimumSize">
456
- <size>
457
- <width>250</width>
458
- <height>0</height>
459
- </size>
460
- </property>
461
- <property name="maximumSize">
462
- <size>
463
- <width>16777215</width>
464
- <height>16777215</height>
465
- </size>
466
- </property>
467
- <property name="font">
468
- <font>
469
- <family>Arial</family>
470
- <pointsize>12</pointsize>
471
- <italic>false</italic>
472
- <bold>false</bold>
473
- </font>
474
- </property>
475
- </widget>
476
- </item>
477
- <item row="4" column="0" colspan="3">
478
- <widget class="QProgressBar" name="progBarFindTargets">
479
- <property name="sizePolicy">
480
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
481
- <horstretch>0</horstretch>
482
- <verstretch>0</verstretch>
483
- </sizepolicy>
484
- </property>
485
- <property name="minimumSize">
486
- <size>
487
- <width>0</width>
488
- <height>0</height>
489
- </size>
490
- </property>
491
- <property name="value">
492
- <number>24</number>
493
- </property>
494
- </widget>
495
- </item>
496
- <item row="2" column="0" colspan="3">
497
- <widget class="QPushButton" name="pbtnFindTargets">
498
- <property name="enabled">
499
- <bool>true</bool>
500
- </property>
501
- <property name="sizePolicy">
502
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
503
- <horstretch>0</horstretch>
504
- <verstretch>0</verstretch>
505
- </sizepolicy>
506
- </property>
507
- <property name="minimumSize">
508
- <size>
509
- <width>0</width>
510
- <height>0</height>
511
- </size>
512
- </property>
513
- <property name="maximumSize">
514
- <size>
515
- <width>16777215</width>
516
- <height>16777215</height>
517
- </size>
518
- </property>
519
- <property name="text">
520
- <string>Find Targets</string>
521
- </property>
522
- </widget>
523
- </item>
524
- <item row="0" column="0">
525
- <widget class="QRadioButton" name="rbtnFeature">
526
- <property name="sizePolicy">
527
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
528
- <horstretch>0</horstretch>
529
- <verstretch>0</verstretch>
530
- </sizepolicy>
531
- </property>
532
- <property name="font">
533
- <font>
534
- <family>Arial</family>
535
- <pointsize>12</pointsize>
536
- <italic>false</italic>
537
- <bold>false</bold>
538
- </font>
539
- </property>
540
- <property name="text">
541
- <string>Feature</string>
542
- </property>
543
- <property name="checked">
544
- <bool>true</bool>
545
- </property>
546
- </widget>
547
- </item>
548
- <item row="0" column="1">
549
- <widget class="QRadioButton" name="rbtnPosition">
550
- <property name="sizePolicy">
551
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
552
- <horstretch>0</horstretch>
553
- <verstretch>0</verstretch>
554
- </sizepolicy>
555
- </property>
556
- <property name="font">
557
- <font>
558
- <family>Arial</family>
559
- <pointsize>12</pointsize>
560
- <italic>false</italic>
561
- <bold>false</bold>
562
- </font>
563
- </property>
564
- <property name="text">
565
- <string>Position</string>
566
- </property>
567
- <property name="iconSize">
568
- <size>
569
- <width>16</width>
570
- <height>16</height>
571
- </size>
572
- </property>
573
- </widget>
574
- </item>
575
- <item row="5" column="0" colspan="3">
576
- <layout class="QHBoxLayout" name="horizontalLayout">
577
- <item>
578
- <widget class="QPushButton" name="pbtnViewTargets">
579
- <property name="sizePolicy">
580
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
581
- <horstretch>0</horstretch>
582
- <verstretch>0</verstretch>
583
- </sizepolicy>
584
- </property>
585
- <property name="minimumSize">
586
- <size>
587
- <width>0</width>
588
- <height>0</height>
589
- </size>
590
- </property>
591
- <property name="text">
592
- <string>View Targets</string>
593
- </property>
594
- </widget>
595
- </item>
596
- <item>
597
- <widget class="QPushButton" name="pbtnGenerateLibrary">
598
- <property name="sizePolicy">
599
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
600
- <horstretch>0</horstretch>
601
- <verstretch>0</verstretch>
602
- </sizepolicy>
603
- </property>
604
- <property name="minimumSize">
605
- <size>
606
- <width>0</width>
607
- <height>0</height>
608
- </size>
609
- </property>
610
- <property name="maximumSize">
611
- <size>
612
- <width>16777215</width>
613
- <height>16777215</height>
614
- </size>
615
- </property>
616
- <property name="text">
617
- <string>Generate Library</string>
618
- </property>
619
- </widget>
620
- </item>
621
- </layout>
622
- </item>
623
- </layout>
624
- </widget>
625
- </item>
626
- </layout>
627
- </item>
628
- <item row="0" column="0">
629
- <widget class="QLabel" name="lblWindowHeading">
630
- <property name="sizePolicy">
631
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
632
- <horstretch>0</horstretch>
633
- <verstretch>0</verstretch>
634
- </sizepolicy>
635
- </property>
636
- <property name="minimumSize">
637
- <size>
638
- <width>0</width>
639
- <height>0</height>
640
- </size>
641
- </property>
642
- <property name="maximumSize">
643
- <size>
644
- <width>16777215</width>
645
- <height>16777215</height>
646
- </size>
647
- </property>
648
- <property name="font">
649
- <font>
650
- <family>Arial</family>
651
- <pointsize>12</pointsize>
652
- <italic>false</italic>
653
- <bold>true</bold>
654
- </font>
655
- </property>
656
- <property name="styleSheet">
657
- <string notr="true"/>
658
- </property>
659
- <property name="text">
660
- <string>CASPER</string>
661
- </property>
662
- </widget>
663
- </item>
664
- <item row="0" column="1">
665
- <widget class="QLabel" name="pbtnThemeToggle">
666
- <property name="minimumSize">
667
- <size>
668
- <width>50</width>
669
- <height>0</height>
670
- </size>
671
- </property>
672
- <property name="maximumSize">
673
- <size>
674
- <width>50</width>
675
- <height>16777215</height>
676
- </size>
677
- </property>
678
- <property name="text">
679
- <string/>
680
- </property>
681
- </widget>
682
- </item>
683
- <item row="2" column="0" colspan="2">
684
- <widget class="QGroupBox" name="grpNavigationMenu">
685
- <property name="sizePolicy">
686
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
687
- <horstretch>0</horstretch>
688
- <verstretch>0</verstretch>
689
- </sizepolicy>
690
- </property>
691
- <property name="title">
692
- <string>CASPER Navigation</string>
693
- </property>
694
- <layout class="QGridLayout" name="gridLayout_2">
695
- <item row="1" column="1">
696
- <widget class="QPushButton" name="pbtnPopulationAnalysis">
697
- <property name="text">
698
- <string>Population Analysis</string>
699
- </property>
700
- </widget>
701
- </item>
702
- <item row="0" column="1">
703
- <widget class="QPushButton" name="pbtnNewEndonuclease">
704
- <property name="text">
705
- <string>Define New Endonuclease</string>
706
- </property>
707
- </widget>
708
- </item>
709
- <item row="1" column="0">
710
- <widget class="QPushButton" name="pbtnMultitargetingAnalysis">
711
- <property name="text">
712
- <string>Multitargeting Analysis</string>
713
- </property>
714
- </widget>
715
- </item>
716
- <item row="2" column="0">
717
- <widget class="QPushButton" name="pbtnCombineFiles">
718
- <property name="text">
719
- <string>Combine Files</string>
720
- </property>
721
- </widget>
722
- </item>
723
- <item row="0" column="0">
724
- <widget class="QPushButton" name="pbtnNewGenome">
725
- <property name="text">
726
- <string>Analyze New Genome</string>
727
- </property>
728
- </widget>
729
- </item>
730
- </layout>
731
- </widget>
732
- </item>
733
- </layout>
734
- </widget>
735
- <action name="actionNew">
736
- <property name="text">
737
- <string>New</string>
738
- </property>
739
- </action>
740
- <action name="actionOpen">
741
- <property name="text">
742
- <string>Open</string>
743
- </property>
744
- </action>
745
- <action name="actionOpen_Recent">
746
- <property name="text">
747
- <string>Open Recent...</string>
748
- </property>
749
- </action>
750
- <action name="actionSave">
751
- <property name="text">
752
- <string>Save</string>
753
- </property>
754
- </action>
755
- <action name="actionExit">
756
- <property name="checkable">
757
- <bool>false</bool>
758
- </property>
759
- <property name="text">
760
- <string>Exit</string>
761
- </property>
762
- <property name="font">
763
- <font>
764
- <family>Arial</family>
765
- </font>
766
- </property>
767
- </action>
768
- <action name="actionUndo">
769
- <property name="text">
770
- <string>Undo</string>
771
- </property>
772
- </action>
773
- <action name="actionCut">
774
- <property name="text">
775
- <string>Cut</string>
776
- </property>
777
- </action>
778
- <action name="actionCopy">
779
- <property name="text">
780
- <string>Copy</string>
781
- </property>
782
- </action>
783
- <action name="actionPaste">
784
- <property name="text">
785
- <string>Paste</string>
786
- </property>
787
- </action>
788
- <action name="actionCut_Rev_Com">
789
- <property name="text">
790
- <string>Cut Rev-Com</string>
791
- </property>
792
- </action>
793
- <action name="actionCopy_Rev_Com">
794
- <property name="text">
795
- <string>Copy Rev-Com</string>
796
- </property>
797
- </action>
798
- <action name="actionPaste_Rev_Com">
799
- <property name="text">
800
- <string>Paste Rev-Com</string>
801
- </property>
802
- </action>
803
- <action name="actionUpload_New_Genome">
804
- <property name="text">
805
- <string>Analyze New Genome</string>
806
- </property>
807
- <property name="font">
808
- <font>
809
- <family>Arial</family>
810
- </font>
811
- </property>
812
- </action>
813
- <action name="actionUpload_New_Endonuclease">
814
- <property name="text">
815
- <string>Define New Endonuclease</string>
816
- </property>
817
- <property name="font">
818
- <font>
819
- <family>Arial</family>
820
- </font>
821
- </property>
822
- </action>
823
- <action name="actOpenGenomeBrowser">
824
- <property name="text">
825
- <string>Open Genome Browser</string>
826
- </property>
827
- <property name="font">
828
- <font>
829
- <family>Arial</family>
830
- </font>
831
- </property>
832
- </action>
833
- <action name="actChangeDirectory">
834
- <property name="text">
835
- <string>Change Database Directory</string>
836
- </property>
837
- <property name="font">
838
- <font>
839
- <family>Arial</family>
840
- </font>
841
- </property>
842
- </action>
843
- <action name="actOpenNCBIBLAST">
844
- <property name="text">
845
- <string>NCBI BLAST</string>
846
- </property>
847
- </action>
848
- <action name="actionExcel_csv">
849
- <property name="text">
850
- <string>Excel (.csv)</string>
851
- </property>
852
- </action>
853
- <action name="actionIDT_Order">
854
- <property name="text">
855
- <string>IDT Order</string>
856
- </property>
857
- </action>
858
- <action name="actionMultitargeting">
859
- <property name="text">
860
- <string>Multitargeting Analysis</string>
861
- </property>
862
- <property name="font">
863
- <font>
864
- <family>Arial</family>
865
- </font>
866
- </property>
867
- </action>
868
- <action name="actionPopulation_Analysis">
869
- <property name="text">
870
- <string>Population Analysis</string>
871
- </property>
872
- </action>
873
- <action name="actionComplete_Target_Analysis">
874
- <property name="text">
875
- <string>Complete Target Analysis</string>
876
- </property>
877
- </action>
878
- <action name="actOpenNCBI">
879
- <property name="text">
880
- <string>National Center for BioTechnology Information</string>
881
- </property>
882
- <property name="font">
883
- <font>
884
- <family>Arial</family>
885
- </font>
886
- </property>
887
- </action>
888
- <action name="actionCasper2">
889
- <property name="text">
890
- <string>CASPER Online</string>
891
- </property>
892
- <property name="font">
893
- <font>
894
- <family>Arial</family>
895
- </font>
896
- </property>
897
- </action>
898
- <action name="actionCo_Targeting">
899
- <property name="text">
900
- <string>Co-Targeting</string>
901
- </property>
902
- </action>
903
- <action name="actOpenRepository">
904
- <property name="text">
905
- <string>Visit Repository</string>
906
- </property>
907
- <property name="font">
908
- <font>
909
- <family>Arial</family>
910
- </font>
911
- </property>
912
- </action>
913
- <action name="actionNew_2">
914
- <property name="text">
915
- <string>New</string>
916
- </property>
917
- </action>
918
- <action name="actiontesting">
919
- <property name="text">
920
- <string>testing</string>
921
- </property>
922
- </action>
923
- <action name="actionAbout_CASPER">
924
- <property name="text">
925
- <string>About CASPER</string>
926
- </property>
927
- </action>
928
- <action name="actionTest">
929
- <property name="text">
930
- <string>Test</string>
931
- </property>
932
- </action>
933
- </widget>
934
- <tabstops>
935
- <tabstop>cmbOrganism</tabstop>
936
- <tabstop>cmbEndonuclease</tabstop>
937
- <tabstop>pbtnNCBIFileSearch</tabstop>
938
- <tabstop>cmbLocalAnnotationFiles</tabstop>
939
- <tabstop>rbtnFeature</tabstop>
940
- <tabstop>rbtnPosition</tabstop>
941
- <tabstop>txtedGeneEntry</tabstop>
942
- <tabstop>pbtnFindTargets</tabstop>
943
- </tabstops>
944
- <resources/>
945
- <connections/>
946
- </ui>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/ui/multitargeting_sql_settings.ui DELETED
@@ -1,149 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <ui version="4.0">
3
- <class>MainWindow</class>
4
- <widget class="QMainWindow" name="MainWindow">
5
- <property name="geometry">
6
- <rect>
7
- <x>0</x>
8
- <y>0</y>
9
- <width>375</width>
10
- <height>140</height>
11
- </rect>
12
- </property>
13
- <property name="font">
14
- <font>
15
- <pointsize>12</pointsize>
16
- </font>
17
- </property>
18
- <property name="windowTitle">
19
- <string>MainWindow</string>
20
- </property>
21
- <widget class="QWidget" name="centralwidget">
22
- <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="1" column="1">
24
- <layout class="QGridLayout" name="gridLayout">
25
- <item row="2" column="0">
26
- <widget class="QLabel" name="label">
27
- <property name="sizePolicy">
28
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
29
- <horstretch>0</horstretch>
30
- <verstretch>0</verstretch>
31
- </sizepolicy>
32
- </property>
33
- <property name="toolTip">
34
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Max table row count represents the max number of repeated seeds to be analyzed and loaded into the table in multitargeting. The &lt;span style=&quot; font-style:italic;&quot;&gt;global analysis&lt;/span&gt; section is not affected by this value. After changing the value, reload the anlysis but clicking the &lt;span style=&quot; font-weight:600;&quot;&gt;Analyze&lt;/span&gt; button.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
35
- </property>
36
- <property name="text">
37
- <string>Max Table Row Count:</string>
38
- </property>
39
- </widget>
40
- </item>
41
- <item row="2" column="1">
42
- <widget class="QLineEdit" name="row_count">
43
- <property name="sizePolicy">
44
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
45
- <horstretch>0</horstretch>
46
- <verstretch>0</verstretch>
47
- </sizepolicy>
48
- </property>
49
- <property name="toolTip">
50
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Default value is 1000. Change to -1 for no limit.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
51
- </property>
52
- <property name="text">
53
- <string>1000</string>
54
- </property>
55
- </widget>
56
- </item>
57
- <item row="1" column="0" colspan="2">
58
- <widget class="Line" name="line">
59
- <property name="orientation">
60
- <enum>Qt::Horizontal</enum>
61
- </property>
62
- </widget>
63
- </item>
64
- <item row="0" column="0" colspan="2">
65
- <widget class="QLabel" name="title">
66
- <property name="sizePolicy">
67
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
68
- <horstretch>0</horstretch>
69
- <verstretch>0</verstretch>
70
- </sizepolicy>
71
- </property>
72
- <property name="text">
73
- <string>SQL Settings</string>
74
- </property>
75
- </widget>
76
- </item>
77
- </layout>
78
- </item>
79
- <item row="1" column="2">
80
- <spacer name="horizontalSpacer_2">
81
- <property name="orientation">
82
- <enum>Qt::Horizontal</enum>
83
- </property>
84
- <property name="sizeType">
85
- <enum>QSizePolicy::Fixed</enum>
86
- </property>
87
- <property name="sizeHint" stdset="0">
88
- <size>
89
- <width>20</width>
90
- <height>20</height>
91
- </size>
92
- </property>
93
- </spacer>
94
- </item>
95
- <item row="1" column="0">
96
- <spacer name="horizontalSpacer">
97
- <property name="orientation">
98
- <enum>Qt::Horizontal</enum>
99
- </property>
100
- <property name="sizeType">
101
- <enum>QSizePolicy::Fixed</enum>
102
- </property>
103
- <property name="sizeHint" stdset="0">
104
- <size>
105
- <width>20</width>
106
- <height>20</height>
107
- </size>
108
- </property>
109
- </spacer>
110
- </item>
111
- <item row="2" column="1">
112
- <spacer name="verticalSpacer">
113
- <property name="orientation">
114
- <enum>Qt::Vertical</enum>
115
- </property>
116
- <property name="sizeType">
117
- <enum>QSizePolicy::Fixed</enum>
118
- </property>
119
- <property name="sizeHint" stdset="0">
120
- <size>
121
- <width>20</width>
122
- <height>20</height>
123
- </size>
124
- </property>
125
- </spacer>
126
- </item>
127
- <item row="0" column="1">
128
- <spacer name="verticalSpacer_2">
129
- <property name="orientation">
130
- <enum>Qt::Vertical</enum>
131
- </property>
132
- <property name="sizeType">
133
- <enum>QSizePolicy::Fixed</enum>
134
- </property>
135
- <property name="sizeHint" stdset="0">
136
- <size>
137
- <width>20</width>
138
- <height>20</height>
139
- </size>
140
- </property>
141
- </spacer>
142
- </item>
143
- </layout>
144
- </widget>
145
- <widget class="QStatusBar" name="statusbar"/>
146
- </widget>
147
- <resources/>
148
- <connections/>
149
- </ui>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/ui/multitargeting_stats.ui DELETED
@@ -1,227 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <ui version="4.0">
3
- <class>MainWindow</class>
4
- <widget class="QMainWindow" name="MainWindow">
5
- <property name="geometry">
6
- <rect>
7
- <x>0</x>
8
- <y>0</y>
9
- <width>350</width>
10
- <height>250</height>
11
- </rect>
12
- </property>
13
- <property name="font">
14
- <font>
15
- <pointsize>12</pointsize>
16
- </font>
17
- </property>
18
- <property name="windowTitle">
19
- <string>MainWindow</string>
20
- </property>
21
- <widget class="QWidget" name="centralwidget">
22
- <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="2" column="1">
24
- <spacer name="verticalSpacer">
25
- <property name="orientation">
26
- <enum>Qt::Vertical</enum>
27
- </property>
28
- <property name="sizeType">
29
- <enum>QSizePolicy::Fixed</enum>
30
- </property>
31
- <property name="sizeHint" stdset="0">
32
- <size>
33
- <width>20</width>
34
- <height>20</height>
35
- </size>
36
- </property>
37
- </spacer>
38
- </item>
39
- <item row="1" column="2">
40
- <spacer name="horizontalSpacer_2">
41
- <property name="orientation">
42
- <enum>Qt::Horizontal</enum>
43
- </property>
44
- <property name="sizeType">
45
- <enum>QSizePolicy::Fixed</enum>
46
- </property>
47
- <property name="sizeHint" stdset="0">
48
- <size>
49
- <width>20</width>
50
- <height>20</height>
51
- </size>
52
- </property>
53
- </spacer>
54
- </item>
55
- <item row="1" column="0">
56
- <spacer name="horizontalSpacer">
57
- <property name="orientation">
58
- <enum>Qt::Horizontal</enum>
59
- </property>
60
- <property name="sizeType">
61
- <enum>QSizePolicy::Fixed</enum>
62
- </property>
63
- <property name="sizeHint" stdset="0">
64
- <size>
65
- <width>20</width>
66
- <height>20</height>
67
- </size>
68
- </property>
69
- </spacer>
70
- </item>
71
- <item row="0" column="1">
72
- <spacer name="verticalSpacer_2">
73
- <property name="orientation">
74
- <enum>Qt::Vertical</enum>
75
- </property>
76
- <property name="sizeType">
77
- <enum>QSizePolicy::Fixed</enum>
78
- </property>
79
- <property name="sizeHint" stdset="0">
80
- <size>
81
- <width>20</width>
82
- <height>20</height>
83
- </size>
84
- </property>
85
- </spacer>
86
- </item>
87
- <item row="1" column="1">
88
- <layout class="QGridLayout" name="gridLayout">
89
- <item row="2" column="0">
90
- <widget class="QLabel" name="label_2">
91
- <property name="sizePolicy">
92
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
93
- <horstretch>0</horstretch>
94
- <verstretch>0</verstretch>
95
- </sizepolicy>
96
- </property>
97
- <property name="text">
98
- <string>Total Number of Repeats:</string>
99
- </property>
100
- </widget>
101
- </item>
102
- <item row="4" column="0">
103
- <widget class="QLabel" name="label_4">
104
- <property name="sizePolicy">
105
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
106
- <horstretch>0</horstretch>
107
- <verstretch>0</verstretch>
108
- </sizepolicy>
109
- </property>
110
- <property name="text">
111
- <string>Median Number of Repeats:</string>
112
- </property>
113
- </widget>
114
- </item>
115
- <item row="5" column="0">
116
- <widget class="QLabel" name="label_5">
117
- <property name="sizePolicy">
118
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
119
- <horstretch>0</horstretch>
120
- <verstretch>0</verstretch>
121
- </sizepolicy>
122
- </property>
123
- <property name="text">
124
- <string>Mode Number of Repeats:</string>
125
- </property>
126
- </widget>
127
- </item>
128
- <item row="3" column="0">
129
- <widget class="QLabel" name="label_3">
130
- <property name="sizePolicy">
131
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
132
- <horstretch>0</horstretch>
133
- <verstretch>0</verstretch>
134
- </sizepolicy>
135
- </property>
136
- <property name="text">
137
- <string>Average Number of Repeats:</string>
138
- </property>
139
- </widget>
140
- </item>
141
- <item row="2" column="1">
142
- <widget class="QLabel" name="nbr_seq">
143
- <property name="sizePolicy">
144
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
145
- <horstretch>0</horstretch>
146
- <verstretch>0</verstretch>
147
- </sizepolicy>
148
- </property>
149
- <property name="text">
150
- <string/>
151
- </property>
152
- </widget>
153
- </item>
154
- <item row="3" column="1">
155
- <widget class="QLabel" name="avg_rep">
156
- <property name="sizePolicy">
157
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
158
- <horstretch>0</horstretch>
159
- <verstretch>0</verstretch>
160
- </sizepolicy>
161
- </property>
162
- <property name="text">
163
- <string/>
164
- </property>
165
- </widget>
166
- </item>
167
- <item row="4" column="1">
168
- <widget class="QLabel" name="med_rep">
169
- <property name="sizePolicy">
170
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
171
- <horstretch>0</horstretch>
172
- <verstretch>0</verstretch>
173
- </sizepolicy>
174
- </property>
175
- <property name="text">
176
- <string/>
177
- </property>
178
- </widget>
179
- </item>
180
- <item row="5" column="1">
181
- <widget class="QLabel" name="mode_rep">
182
- <property name="sizePolicy">
183
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
184
- <horstretch>0</horstretch>
185
- <verstretch>0</verstretch>
186
- </sizepolicy>
187
- </property>
188
- <property name="text">
189
- <string/>
190
- </property>
191
- </widget>
192
- </item>
193
- <item row="0" column="0" colspan="2">
194
- <widget class="QLabel" name="title">
195
- <property name="sizePolicy">
196
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
197
- <horstretch>0</horstretch>
198
- <verstretch>0</verstretch>
199
- </sizepolicy>
200
- </property>
201
- <property name="text">
202
- <string>Multitargeting Statistics</string>
203
- </property>
204
- </widget>
205
- </item>
206
- <item row="1" column="0" colspan="2">
207
- <widget class="Line" name="line">
208
- <property name="sizePolicy">
209
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
210
- <horstretch>0</horstretch>
211
- <verstretch>0</verstretch>
212
- </sizepolicy>
213
- </property>
214
- <property name="orientation">
215
- <enum>Qt::Horizontal</enum>
216
- </property>
217
- </widget>
218
- </item>
219
- </layout>
220
- </item>
221
- </layout>
222
- </widget>
223
- <widget class="QStatusBar" name="statusbar"/>
224
- </widget>
225
- <resources/>
226
- <connections/>
227
- </ui>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/ui/multitargeting_window.ui CHANGED
@@ -42,23 +42,37 @@
42
  <string>Select Organism and Endonuclease:</string>
43
  </property>
44
  <layout class="QGridLayout" name="gridLayout_8">
45
- <item row="1" column="0">
46
- <widget class="QComboBox" name="cmbOrganism">
 
 
 
 
 
 
 
 
 
 
47
  <property name="sizePolicy">
48
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
49
  <horstretch>0</horstretch>
50
  <verstretch>0</verstretch>
51
  </sizepolicy>
52
  </property>
53
- <property name="minimumSize">
54
- <size>
55
- <width>0</width>
56
- <height>0</height>
57
- </size>
58
  </property>
59
  </widget>
60
  </item>
61
- <item row="2" column="0" colspan="2">
 
 
 
 
 
 
 
62
  <layout class="QHBoxLayout" name="horizontalLayout">
63
  <item>
64
  <widget class="QPushButton" name="pbtnAnalyze">
@@ -79,46 +93,42 @@
79
  </property>
80
  </widget>
81
  </item>
82
- <item>
83
- <widget class="QToolButton" name="tbtnSQLSettings">
84
- <property name="minimumSize">
85
- <size>
86
- <width>0</width>
87
- <height>0</height>
88
- </size>
89
- </property>
90
- <property name="text">
91
- <string>...</string>
92
- </property>
93
- </widget>
94
- </item>
95
  </layout>
96
  </item>
97
  <item row="0" column="1">
98
- <widget class="QLabel" name="lblEndonuclease">
99
  <property name="sizePolicy">
100
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
101
  <horstretch>0</horstretch>
102
  <verstretch>0</verstretch>
103
  </sizepolicy>
104
  </property>
105
- <property name="text">
106
- <string>Endonuclease</string>
 
 
 
107
  </property>
108
  </widget>
109
  </item>
110
- <item row="4" column="0" colspan="2">
111
- <widget class="QTableWidget" name="tblSeeds">
 
 
 
 
 
 
112
  <property name="minimumSize">
113
  <size>
114
- <width>400</width>
115
  <height>0</height>
116
  </size>
117
  </property>
118
  </widget>
119
  </item>
120
- <item row="0" column="0">
121
- <widget class="QLabel" name="lblOrganism">
122
  <property name="sizePolicy">
123
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
124
  <horstretch>0</horstretch>
@@ -126,30 +136,39 @@
126
  </sizepolicy>
127
  </property>
128
  <property name="text">
129
- <string>Organism</string>
130
  </property>
131
  </widget>
132
  </item>
133
- <item row="1" column="1">
134
- <widget class="QComboBox" name="cmbEndonuclease">
135
  <property name="sizePolicy">
136
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
137
  <horstretch>0</horstretch>
138
  <verstretch>0</verstretch>
139
  </sizepolicy>
140
  </property>
141
- <property name="minimumSize">
142
- <size>
143
- <width>0</width>
144
- <height>0</height>
145
- </size>
146
  </property>
147
  </widget>
148
  </item>
149
- <item row="3" column="0">
150
- <widget class="QCheckBox" name="chkSelectAll">
 
 
 
 
 
 
 
 
 
151
  <property name="text">
152
- <string>Select All</string>
153
  </property>
154
  </widget>
155
  </item>
@@ -228,7 +247,7 @@
228
  <rect>
229
  <x>0</x>
230
  <y>0</y>
231
- <width>369</width>
232
  <height>112</height>
233
  </rect>
234
  </property>
@@ -277,25 +296,6 @@
277
  </property>
278
  <layout class="QGridLayout" name="gridLayout_7">
279
  <item row="0" column="0">
280
- <widget class="QPushButton" name="pbtnStatisticsOverview">
281
- <property name="sizePolicy">
282
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
283
- <horstretch>0</horstretch>
284
- <verstretch>0</verstretch>
285
- </sizepolicy>
286
- </property>
287
- <property name="minimumSize">
288
- <size>
289
- <width>0</width>
290
- <height>0</height>
291
- </size>
292
- </property>
293
- <property name="text">
294
- <string>Statistics Overview</string>
295
- </property>
296
- </widget>
297
- </item>
298
- <item row="1" column="0">
299
  <widget class="QTabWidget" name="tabsGlobalAnalysis">
300
  <property name="sizePolicy">
301
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@@ -304,8 +304,129 @@
304
  </sizepolicy>
305
  </property>
306
  <property name="currentIndex">
307
- <number>1</number>
308
  </property>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  <widget class="QWidget" name="tabRepeatsVsSeed">
310
  <attribute name="title">
311
  <string>Repeats per Seed ID Number</string>
@@ -345,27 +466,8 @@
345
  </property>
346
  </widget>
347
  </item>
348
- <item row="3" column="0" alignment="Qt::AlignLeft">
349
- <widget class="QPushButton" name="pbtnBank">
350
- <property name="sizePolicy">
351
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
352
- <horstretch>0</horstretch>
353
- <verstretch>0</verstretch>
354
- </sizepolicy>
355
- </property>
356
- <property name="minimumSize">
357
- <size>
358
- <width>125</width>
359
- <height>0</height>
360
- </size>
361
- </property>
362
- <property name="text">
363
- <string>Back</string>
364
- </property>
365
- </widget>
366
- </item>
367
  <item row="3" column="1" alignment="Qt::AlignRight">
368
- <widget class="QPushButton" name="pbtnExport">
369
  <property name="sizePolicy">
370
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
371
  <horstretch>0</horstretch>
 
42
  <string>Select Organism and Endonuclease:</string>
43
  </property>
44
  <layout class="QGridLayout" name="gridLayout_8">
45
+ <item row="8" column="0" colspan="3">
46
+ <widget class="QTableWidget" name="tblSeeds">
47
+ <property name="minimumSize">
48
+ <size>
49
+ <width>400</width>
50
+ <height>0</height>
51
+ </size>
52
+ </property>
53
+ </widget>
54
+ </item>
55
+ <item row="0" column="0">
56
+ <widget class="QLabel" name="lblOrganism">
57
  <property name="sizePolicy">
58
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
59
  <horstretch>0</horstretch>
60
  <verstretch>0</verstretch>
61
  </sizepolicy>
62
  </property>
63
+ <property name="text">
64
+ <string>Organism:</string>
 
 
 
65
  </property>
66
  </widget>
67
  </item>
68
+ <item row="7" column="0">
69
+ <widget class="QCheckBox" name="chkSelectAll">
70
+ <property name="text">
71
+ <string>Select All</string>
72
+ </property>
73
+ </widget>
74
+ </item>
75
+ <item row="6" column="0" colspan="3">
76
  <layout class="QHBoxLayout" name="horizontalLayout">
77
  <item>
78
  <widget class="QPushButton" name="pbtnAnalyze">
 
93
  </property>
94
  </widget>
95
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  </layout>
97
  </item>
98
  <item row="0" column="1">
99
+ <widget class="QComboBox" name="cmbOrganism">
100
  <property name="sizePolicy">
101
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
102
  <horstretch>0</horstretch>
103
  <verstretch>0</verstretch>
104
  </sizepolicy>
105
  </property>
106
+ <property name="minimumSize">
107
+ <size>
108
+ <width>0</width>
109
+ <height>0</height>
110
+ </size>
111
  </property>
112
  </widget>
113
  </item>
114
+ <item row="1" column="1">
115
+ <widget class="QComboBox" name="cmbEndonuclease">
116
+ <property name="sizePolicy">
117
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
118
+ <horstretch>0</horstretch>
119
+ <verstretch>0</verstretch>
120
+ </sizepolicy>
121
+ </property>
122
  <property name="minimumSize">
123
  <size>
124
+ <width>0</width>
125
  <height>0</height>
126
  </size>
127
  </property>
128
  </widget>
129
  </item>
130
+ <item row="1" column="0">
131
+ <widget class="QLabel" name="lblEndonuclease">
132
  <property name="sizePolicy">
133
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
134
  <horstretch>0</horstretch>
 
136
  </sizepolicy>
137
  </property>
138
  <property name="text">
139
+ <string>Endonuclease:</string>
140
  </property>
141
  </widget>
142
  </item>
143
+ <item row="2" column="1">
144
+ <widget class="QLineEdit" name="ledMaxResults">
145
  <property name="sizePolicy">
146
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
147
  <horstretch>0</horstretch>
148
  <verstretch>0</verstretch>
149
  </sizepolicy>
150
  </property>
151
+ <property name="toolTip">
152
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Default value is 1000. Change to -1 for no limit.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
153
+ </property>
154
+ <property name="text">
155
+ <string>1000</string>
156
  </property>
157
  </widget>
158
  </item>
159
+ <item row="2" column="0">
160
+ <widget class="QLabel" name="lblMaxResults">
161
+ <property name="sizePolicy">
162
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
163
+ <horstretch>0</horstretch>
164
+ <verstretch>0</verstretch>
165
+ </sizepolicy>
166
+ </property>
167
+ <property name="toolTip">
168
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Max table row count represents the max number of repeated seeds to be analyzed and loaded into the table in multitargeting. The &lt;span style=&quot; font-style:italic;&quot;&gt;global analysis&lt;/span&gt; section is not affected by this value. After changing the value, reload the anlysis but clicking the &lt;span style=&quot; font-weight:600;&quot;&gt;Analyze&lt;/span&gt; button.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
169
+ </property>
170
  <property name="text">
171
+ <string>Max Results:</string>
172
  </property>
173
  </widget>
174
  </item>
 
247
  <rect>
248
  <x>0</x>
249
  <y>0</y>
250
+ <width>374</width>
251
  <height>112</height>
252
  </rect>
253
  </property>
 
296
  </property>
297
  <layout class="QGridLayout" name="gridLayout_7">
298
  <item row="0" column="0">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  <widget class="QTabWidget" name="tabsGlobalAnalysis">
300
  <property name="sizePolicy">
301
  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
 
304
  </sizepolicy>
305
  </property>
306
  <property name="currentIndex">
307
+ <number>0</number>
308
  </property>
309
+ <widget class="QWidget" name="tabStatisticsOverview">
310
+ <attribute name="title">
311
+ <string>Statistics Overview</string>
312
+ </attribute>
313
+ <widget class="QWidget" name="layoutWidget">
314
+ <property name="geometry">
315
+ <rect>
316
+ <x>10</x>
317
+ <y>10</y>
318
+ <width>371</width>
319
+ <height>133</height>
320
+ </rect>
321
+ </property>
322
+ <layout class="QGridLayout" name="gridLayout_3">
323
+ <item row="2" column="1">
324
+ <widget class="QLabel" name="lblMedianRepeatsValue">
325
+ <property name="sizePolicy">
326
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
327
+ <horstretch>0</horstretch>
328
+ <verstretch>0</verstretch>
329
+ </sizepolicy>
330
+ </property>
331
+ <property name="text">
332
+ <string/>
333
+ </property>
334
+ </widget>
335
+ </item>
336
+ <item row="0" column="1">
337
+ <widget class="QLabel" name="lblTotalRepeatsValue">
338
+ <property name="sizePolicy">
339
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
340
+ <horstretch>0</horstretch>
341
+ <verstretch>0</verstretch>
342
+ </sizepolicy>
343
+ </property>
344
+ <property name="text">
345
+ <string/>
346
+ </property>
347
+ </widget>
348
+ </item>
349
+ <item row="2" column="0">
350
+ <widget class="QLabel" name="lblMedianRepeats">
351
+ <property name="sizePolicy">
352
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
353
+ <horstretch>0</horstretch>
354
+ <verstretch>0</verstretch>
355
+ </sizepolicy>
356
+ </property>
357
+ <property name="text">
358
+ <string>Median Number of Repeats:</string>
359
+ </property>
360
+ </widget>
361
+ </item>
362
+ <item row="1" column="0">
363
+ <widget class="QLabel" name="lblAverageRepeats">
364
+ <property name="sizePolicy">
365
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
366
+ <horstretch>0</horstretch>
367
+ <verstretch>0</verstretch>
368
+ </sizepolicy>
369
+ </property>
370
+ <property name="text">
371
+ <string>Average Number of Repeats:</string>
372
+ </property>
373
+ </widget>
374
+ </item>
375
+ <item row="3" column="1">
376
+ <widget class="QLabel" name="lblModeRepeatsValue">
377
+ <property name="sizePolicy">
378
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
379
+ <horstretch>0</horstretch>
380
+ <verstretch>0</verstretch>
381
+ </sizepolicy>
382
+ </property>
383
+ <property name="text">
384
+ <string/>
385
+ </property>
386
+ </widget>
387
+ </item>
388
+ <item row="3" column="0">
389
+ <widget class="QLabel" name="lblModeRepeats">
390
+ <property name="sizePolicy">
391
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
392
+ <horstretch>0</horstretch>
393
+ <verstretch>0</verstretch>
394
+ </sizepolicy>
395
+ </property>
396
+ <property name="text">
397
+ <string>Mode Number of Repeats:</string>
398
+ </property>
399
+ </widget>
400
+ </item>
401
+ <item row="0" column="0">
402
+ <widget class="QLabel" name="lblTotalRepeats">
403
+ <property name="sizePolicy">
404
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
405
+ <horstretch>0</horstretch>
406
+ <verstretch>0</verstretch>
407
+ </sizepolicy>
408
+ </property>
409
+ <property name="text">
410
+ <string>Total Number of Repeats:</string>
411
+ </property>
412
+ </widget>
413
+ </item>
414
+ <item row="1" column="1">
415
+ <widget class="QLabel" name="lblAverageRepeatsValue">
416
+ <property name="sizePolicy">
417
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
418
+ <horstretch>0</horstretch>
419
+ <verstretch>0</verstretch>
420
+ </sizepolicy>
421
+ </property>
422
+ <property name="text">
423
+ <string/>
424
+ </property>
425
+ </widget>
426
+ </item>
427
+ </layout>
428
+ </widget>
429
+ </widget>
430
  <widget class="QWidget" name="tabRepeatsVsSeed">
431
  <attribute name="title">
432
  <string>Repeats per Seed ID Number</string>
 
466
  </property>
467
  </widget>
468
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  <item row="3" column="1" alignment="Qt::AlignRight">
470
+ <widget class="QPushButton" name="pbtnExportSelectedgRNAs">
471
  <property name="sizePolicy">
472
  <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
473
  <horstretch>0</horstretch>
src/ui/{ncbi_window_v2.ui → ncbi.ui} RENAMED
File without changes
src/ui/ncbi_window.ui DELETED
@@ -1,335 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <ui version="4.0">
3
- <class>MainWindow</class>
4
- <widget class="QMainWindow" name="MainWindow">
5
- <property name="geometry">
6
- <rect>
7
- <x>0</x>
8
- <y>0</y>
9
- <width>865</width>
10
- <height>723</height>
11
- </rect>
12
- </property>
13
- <property name="font">
14
- <font>
15
- <pointsize>12</pointsize>
16
- </font>
17
- </property>
18
- <property name="windowTitle">
19
- <string>MainWindow</string>
20
- </property>
21
- <widget class="QWidget" name="centralwidget">
22
- <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="2" column="0">
24
- <layout class="QGridLayout" name="gridLayout">
25
- <item row="2" column="1">
26
- <widget class="QGroupBox" name="Step3">
27
- <property name="sizePolicy">
28
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
29
- <horstretch>0</horstretch>
30
- <verstretch>0</verstretch>
31
- </sizepolicy>
32
- </property>
33
- <property name="title">
34
- <string>Step 3: Download Files</string>
35
- </property>
36
- <layout class="QGridLayout" name="gridLayout_5">
37
- <item row="2" column="1" colspan="2">
38
- <widget class="QLabel" name="progressLabel">
39
- <property name="text">
40
- <string/>
41
- </property>
42
- </widget>
43
- </item>
44
- <item row="3" column="0" colspan="3">
45
- <widget class="QProgressBar" name="progressBar">
46
- <property name="value">
47
- <number>24</number>
48
- </property>
49
- </widget>
50
- </item>
51
- <item row="0" column="2">
52
- <widget class="QRadioButton" name="genbank_checkbox">
53
- <property name="text">
54
- <string>GenBank</string>
55
- </property>
56
- <property name="checked">
57
- <bool>false</bool>
58
- </property>
59
- </widget>
60
- </item>
61
- <item row="0" column="0">
62
- <widget class="QLabel" name="label_6">
63
- <property name="toolTip">
64
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;These are the databases that files are downloaded from. Refseq is considered the gold standard for annotations and assemblies. It is HIGHLY recommended to use Refseq if available for the genome of interest.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
65
- </property>
66
- <property name="text">
67
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;*&lt;/span&gt; Collections:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
68
- </property>
69
- </widget>
70
- </item>
71
- <item row="1" column="2">
72
- <widget class="QCheckBox" name="gbff_checkbox">
73
- <property name="text">
74
- <string>GBFF</string>
75
- </property>
76
- <property name="checked">
77
- <bool>true</bool>
78
- </property>
79
- </widget>
80
- </item>
81
- <item row="1" column="0">
82
- <widget class="QLabel" name="label_7">
83
- <property name="toolTip">
84
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;These are the file types that you are choosing to download. GBFF = annotation file, FASTA/FNA = genomic sequence file.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
85
- </property>
86
- <property name="text">
87
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;*&lt;/span&gt; File Types:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
88
- </property>
89
- </widget>
90
- </item>
91
- <item row="1" column="1">
92
- <widget class="QCheckBox" name="fna_checkbox">
93
- <property name="text">
94
- <string>FASTA/FNA</string>
95
- </property>
96
- <property name="checked">
97
- <bool>true</bool>
98
- </property>
99
- </widget>
100
- </item>
101
- <item row="2" column="0">
102
- <widget class="QPushButton" name="download_button">
103
- <property name="minimumSize">
104
- <size>
105
- <width>125</width>
106
- <height>0</height>
107
- </size>
108
- </property>
109
- <property name="text">
110
- <string>Download Files</string>
111
- </property>
112
- </widget>
113
- </item>
114
- <item row="0" column="1">
115
- <widget class="QRadioButton" name="refseq_checkbox">
116
- <property name="text">
117
- <string>RefSeq</string>
118
- </property>
119
- <property name="checked">
120
- <bool>true</bool>
121
- </property>
122
- </widget>
123
- </item>
124
- <item row="5" column="0" colspan="3">
125
- <layout class="QFormLayout" name="formLayout">
126
- <property name="fieldGrowthPolicy">
127
- <enum>QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow</enum>
128
- </property>
129
- </layout>
130
- </item>
131
- </layout>
132
- </widget>
133
- </item>
134
- <item row="1" column="0" colspan="2">
135
- <widget class="Line" name="line">
136
- <property name="orientation">
137
- <enum>Qt::Orientation::Horizontal</enum>
138
- </property>
139
- </widget>
140
- </item>
141
- <item row="3" column="0" colspan="2">
142
- <widget class="QGroupBox" name="Step2">
143
- <property name="title">
144
- <string>Step 2: Search and Select Files</string>
145
- </property>
146
- <layout class="QGridLayout" name="gridLayout_6">
147
- <item row="0" column="1" alignment="Qt::AlignmentFlag::AlignRight">
148
- <widget class="QCheckBox" name="all_rows">
149
- <property name="text">
150
- <string>Select all rows</string>
151
- </property>
152
- </widget>
153
- </item>
154
- <item row="0" column="0" alignment="Qt::AlignmentFlag::AlignLeft">
155
- <widget class="QPushButton" name="search_button">
156
- <property name="minimumSize">
157
- <size>
158
- <width>125</width>
159
- <height>0</height>
160
- </size>
161
- </property>
162
- <property name="text">
163
- <string>Search</string>
164
- </property>
165
- </widget>
166
- </item>
167
- <item row="1" column="0" colspan="2">
168
- <widget class="QTableView" name="ncbi_table">
169
- <property name="layoutDirection">
170
- <enum>Qt::LayoutDirection::LeftToRight</enum>
171
- </property>
172
- </widget>
173
- </item>
174
- </layout>
175
- </widget>
176
- </item>
177
- <item row="2" column="0">
178
- <widget class="QGroupBox" name="Step1">
179
- <property name="sizePolicy">
180
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
181
- <horstretch>0</horstretch>
182
- <verstretch>0</verstretch>
183
- </sizepolicy>
184
- </property>
185
- <property name="title">
186
- <string>Step 1: Input Search Options</string>
187
- </property>
188
- <layout class="QGridLayout" name="gridLayout_4">
189
- <item row="3" column="1">
190
- <widget class="QCheckBox" name="yes_box">
191
- <property name="text">
192
- <string/>
193
- </property>
194
- </widget>
195
- </item>
196
- <item row="0" column="0">
197
- <widget class="QLabel" name="label">
198
- <property name="sizePolicy">
199
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
200
- <horstretch>0</horstretch>
201
- <verstretch>0</verstretch>
202
- </sizepolicy>
203
- </property>
204
- <property name="text">
205
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;* &lt;/span&gt;Organism&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
206
- </property>
207
- </widget>
208
- </item>
209
- <item row="0" column="1">
210
- <widget class="QLineEdit" name="organism_line_edit">
211
- <property name="placeholderText">
212
- <string>Ex. Escherichia coli</string>
213
- </property>
214
- </widget>
215
- </item>
216
- <item row="3" column="0">
217
- <widget class="QLabel" name="label_5">
218
- <property name="sizePolicy">
219
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
220
- <horstretch>0</horstretch>
221
- <verstretch>0</verstretch>
222
- </sizepolicy>
223
- </property>
224
- <property name="toolTip">
225
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Only display entries classified as &amp;quot;Complete Genomes&amp;quot; by NCBI.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
226
- </property>
227
- <property name="text">
228
- <string>Complete Genomes Only</string>
229
- </property>
230
- </widget>
231
- </item>
232
- <item row="2" column="1">
233
- <widget class="QLineEdit" name="ret_max_line_edit">
234
- <property name="text">
235
- <string>100</string>
236
- </property>
237
- </widget>
238
- </item>
239
- <item row="1" column="0">
240
- <widget class="QLabel" name="label_3">
241
- <property name="sizePolicy">
242
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
243
- <horstretch>0</horstretch>
244
- <verstretch>0</verstretch>
245
- </sizepolicy>
246
- </property>
247
- <property name="text">
248
- <string>Strain Designation</string>
249
- </property>
250
- </widget>
251
- </item>
252
- <item row="1" column="1">
253
- <widget class="QLineEdit" name="infra_name_line_edit">
254
- <property name="placeholderText">
255
- <string>Ex. K-12</string>
256
- </property>
257
- </widget>
258
- </item>
259
- <item row="2" column="0">
260
- <widget class="QLabel" name="label_4">
261
- <property name="sizePolicy">
262
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
263
- <horstretch>0</horstretch>
264
- <verstretch>0</verstretch>
265
- </sizepolicy>
266
- </property>
267
- <property name="toolTip">
268
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Number of search results to return.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
269
- </property>
270
- <property name="text">
271
- <string>Return Max (Default = 100)</string>
272
- </property>
273
- </widget>
274
- </item>
275
- </layout>
276
- </widget>
277
- </item>
278
- <item row="4" column="0" alignment="Qt::AlignmentFlag::AlignLeft">
279
- <widget class="QPushButton" name="back_button">
280
- <property name="minimumSize">
281
- <size>
282
- <width>125</width>
283
- <height>0</height>
284
- </size>
285
- </property>
286
- <property name="text">
287
- <string>Back</string>
288
- </property>
289
- </widget>
290
- </item>
291
- <item row="0" column="1">
292
- <widget class="QLabel" name="label_2">
293
- <property name="text">
294
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;* Required&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
295
- </property>
296
- <property name="alignment">
297
- <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
298
- </property>
299
- </widget>
300
- </item>
301
- <item row="0" column="0">
302
- <widget class="QLabel" name="title">
303
- <property name="sizePolicy">
304
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
305
- <horstretch>0</horstretch>
306
- <verstretch>0</verstretch>
307
- </sizepolicy>
308
- </property>
309
- <property name="text">
310
- <string>NCBI Tool</string>
311
- </property>
312
- </widget>
313
- </item>
314
- </layout>
315
- </item>
316
- </layout>
317
- </widget>
318
- <widget class="QStatusBar" name="statusbar"/>
319
- </widget>
320
- <tabstops>
321
- <tabstop>organism_line_edit</tabstop>
322
- <tabstop>infra_name_line_edit</tabstop>
323
- <tabstop>ret_max_line_edit</tabstop>
324
- <tabstop>yes_box</tabstop>
325
- <tabstop>search_button</tabstop>
326
- <tabstop>all_rows</tabstop>
327
- <tabstop>ncbi_table</tabstop>
328
- <tabstop>genbank_checkbox</tabstop>
329
- <tabstop>refseq_checkbox</tabstop>
330
- <tabstop>download_button</tabstop>
331
- <tabstop>back_button</tabstop>
332
- </tabstops>
333
- <resources/>
334
- <connections/>
335
- </ui>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/ui/ncbi_window_v2 copy.ui DELETED
@@ -1,495 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <ui version="4.0">
3
- <class>MainWindow</class>
4
- <widget class="QMainWindow" name="MainWindow">
5
- <property name="geometry">
6
- <rect>
7
- <x>0</x>
8
- <y>0</y>
9
- <width>865</width>
10
- <height>723</height>
11
- </rect>
12
- </property>
13
- <property name="font">
14
- <font>
15
- <pointsize>12</pointsize>
16
- </font>
17
- </property>
18
- <property name="windowTitle">
19
- <string>MainWindow</string>
20
- </property>
21
- <widget class="QWidget" name="centralwidget">
22
- <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="2" column="1">
24
- <spacer name="verticalSpacer">
25
- <property name="orientation">
26
- <enum>Qt::Vertical</enum>
27
- </property>
28
- <property name="sizeType">
29
- <enum>QSizePolicy::Fixed</enum>
30
- </property>
31
- <property name="sizeHint" stdset="0">
32
- <size>
33
- <width>20</width>
34
- <height>20</height>
35
- </size>
36
- </property>
37
- </spacer>
38
- </item>
39
- <item row="1" column="0">
40
- <spacer name="horizontalSpacer">
41
- <property name="orientation">
42
- <enum>Qt::Horizontal</enum>
43
- </property>
44
- <property name="sizeType">
45
- <enum>QSizePolicy::Fixed</enum>
46
- </property>
47
- <property name="sizeHint" stdset="0">
48
- <size>
49
- <width>20</width>
50
- <height>20</height>
51
- </size>
52
- </property>
53
- </spacer>
54
- </item>
55
- <item row="1" column="1">
56
- <layout class="QGridLayout" name="gridLayout">
57
- <item row="3" column="0" colspan="2">
58
- <widget class="QGroupBox" name="Step2">
59
- <property name="title">
60
- <string>Step 2: Search and Select Files</string>
61
- </property>
62
- <layout class="QGridLayout" name="gridLayout_6">
63
- <item row="1" column="0" alignment="Qt::AlignLeft">
64
- <widget class="QPushButton" name="search_button">
65
- <property name="minimumSize">
66
- <size>
67
- <width>125</width>
68
- <height>0</height>
69
- </size>
70
- </property>
71
- <property name="text">
72
- <string>Search</string>
73
- </property>
74
- </widget>
75
- </item>
76
- <item row="3" column="0" colspan="3">
77
- <spacer name="verticalSpacer_6">
78
- <property name="orientation">
79
- <enum>Qt::Vertical</enum>
80
- </property>
81
- <property name="sizeType">
82
- <enum>QSizePolicy::Fixed</enum>
83
- </property>
84
- <property name="sizeHint" stdset="0">
85
- <size>
86
- <width>10</width>
87
- <height>10</height>
88
- </size>
89
- </property>
90
- </spacer>
91
- </item>
92
- <item row="0" column="0" colspan="3">
93
- <spacer name="verticalSpacer_5">
94
- <property name="orientation">
95
- <enum>Qt::Vertical</enum>
96
- </property>
97
- <property name="sizeType">
98
- <enum>QSizePolicy::Fixed</enum>
99
- </property>
100
- <property name="sizeHint" stdset="0">
101
- <size>
102
- <width>10</width>
103
- <height>10</height>
104
- </size>
105
- </property>
106
- </spacer>
107
- </item>
108
- <item row="2" column="0" colspan="3">
109
- <widget class="QTableView" name="ncbi_table">
110
- <property name="layoutDirection">
111
- <enum>Qt::LeftToRight</enum>
112
- </property>
113
- </widget>
114
- </item>
115
- <item row="1" column="2" alignment="Qt::AlignRight">
116
- <widget class="QCheckBox" name="all_rows">
117
- <property name="text">
118
- <string>Select all rows</string>
119
- </property>
120
- </widget>
121
- </item>
122
- </layout>
123
- </widget>
124
- </item>
125
- <item row="0" column="1">
126
- <widget class="QLabel" name="label_2">
127
- <property name="text">
128
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;* Required&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
129
- </property>
130
- <property name="alignment">
131
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
132
- </property>
133
- </widget>
134
- </item>
135
- <item row="1" column="0" colspan="2">
136
- <widget class="Line" name="line">
137
- <property name="orientation">
138
- <enum>Qt::Horizontal</enum>
139
- </property>
140
- </widget>
141
- </item>
142
- <item row="2" column="0">
143
- <widget class="QGroupBox" name="Step1">
144
- <property name="sizePolicy">
145
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
146
- <horstretch>0</horstretch>
147
- <verstretch>0</verstretch>
148
- </sizepolicy>
149
- </property>
150
- <property name="title">
151
- <string>Step 1: Input Search Options</string>
152
- </property>
153
- <layout class="QGridLayout" name="gridLayout_4">
154
- <item row="2" column="0">
155
- <widget class="QLabel" name="label_3">
156
- <property name="sizePolicy">
157
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
158
- <horstretch>0</horstretch>
159
- <verstretch>0</verstretch>
160
- </sizepolicy>
161
- </property>
162
- <property name="text">
163
- <string>Strain Designation</string>
164
- </property>
165
- </widget>
166
- </item>
167
- <item row="3" column="0">
168
- <widget class="QLabel" name="label_4">
169
- <property name="sizePolicy">
170
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
171
- <horstretch>0</horstretch>
172
- <verstretch>0</verstretch>
173
- </sizepolicy>
174
- </property>
175
- <property name="toolTip">
176
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Number of search results to return.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
177
- </property>
178
- <property name="text">
179
- <string>Return Max (Default = 100)</string>
180
- </property>
181
- </widget>
182
- </item>
183
- <item row="4" column="0">
184
- <widget class="QLabel" name="label_5">
185
- <property name="sizePolicy">
186
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
187
- <horstretch>0</horstretch>
188
- <verstretch>0</verstretch>
189
- </sizepolicy>
190
- </property>
191
- <property name="toolTip">
192
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Only display entries classified as &amp;quot;Complete Genomes&amp;quot; by NCBI.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
193
- </property>
194
- <property name="text">
195
- <string>Complete Genomes Only</string>
196
- </property>
197
- </widget>
198
- </item>
199
- <item row="1" column="0">
200
- <widget class="QLabel" name="label">
201
- <property name="sizePolicy">
202
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
203
- <horstretch>0</horstretch>
204
- <verstretch>0</verstretch>
205
- </sizepolicy>
206
- </property>
207
- <property name="text">
208
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;* &lt;/span&gt;Organism&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
209
- </property>
210
- </widget>
211
- </item>
212
- <item row="1" column="1">
213
- <widget class="QLineEdit" name="organism_line_edit">
214
- <property name="placeholderText">
215
- <string>Ex. Escherichia coli</string>
216
- </property>
217
- </widget>
218
- </item>
219
- <item row="2" column="1">
220
- <widget class="QLineEdit" name="infra_name_line_edit">
221
- <property name="placeholderText">
222
- <string>Ex. K-12</string>
223
- </property>
224
- </widget>
225
- </item>
226
- <item row="3" column="1">
227
- <widget class="QLineEdit" name="ret_max_line_edit">
228
- <property name="text">
229
- <string>100</string>
230
- </property>
231
- </widget>
232
- </item>
233
- <item row="4" column="1">
234
- <widget class="QCheckBox" name="yes_box">
235
- <property name="text">
236
- <string/>
237
- </property>
238
- </widget>
239
- </item>
240
- <item row="5" column="0" colspan="2">
241
- <spacer name="verticalSpacer_3">
242
- <property name="orientation">
243
- <enum>Qt::Vertical</enum>
244
- </property>
245
- <property name="sizeType">
246
- <enum>QSizePolicy::Fixed</enum>
247
- </property>
248
- <property name="sizeHint" stdset="0">
249
- <size>
250
- <width>10</width>
251
- <height>10</height>
252
- </size>
253
- </property>
254
- </spacer>
255
- </item>
256
- <item row="0" column="0" colspan="2">
257
- <spacer name="verticalSpacer_4">
258
- <property name="orientation">
259
- <enum>Qt::Vertical</enum>
260
- </property>
261
- <property name="sizeType">
262
- <enum>QSizePolicy::Fixed</enum>
263
- </property>
264
- <property name="sizeHint" stdset="0">
265
- <size>
266
- <width>10</width>
267
- <height>10</height>
268
- </size>
269
- </property>
270
- </spacer>
271
- </item>
272
- </layout>
273
- </widget>
274
- </item>
275
- <item row="0" column="0">
276
- <widget class="QLabel" name="title">
277
- <property name="sizePolicy">
278
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
279
- <horstretch>0</horstretch>
280
- <verstretch>0</verstretch>
281
- </sizepolicy>
282
- </property>
283
- <property name="text">
284
- <string>NCBI Tool</string>
285
- </property>
286
- </widget>
287
- </item>
288
- <item row="2" column="1">
289
- <widget class="QGroupBox" name="Step3">
290
- <property name="sizePolicy">
291
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
292
- <horstretch>0</horstretch>
293
- <verstretch>0</verstretch>
294
- </sizepolicy>
295
- </property>
296
- <property name="title">
297
- <string>Step 3: Download Files</string>
298
- </property>
299
- <layout class="QGridLayout" name="gridLayout_5">
300
- <item row="7" column="0" colspan="3">
301
- <spacer name="verticalSpacer_8">
302
- <property name="orientation">
303
- <enum>Qt::Vertical</enum>
304
- </property>
305
- <property name="sizeType">
306
- <enum>QSizePolicy::Fixed</enum>
307
- </property>
308
- <property name="sizeHint" stdset="0">
309
- <size>
310
- <width>10</width>
311
- <height>10</height>
312
- </size>
313
- </property>
314
- </spacer>
315
- </item>
316
- <item row="3" column="0">
317
- <widget class="QPushButton" name="download_button">
318
- <property name="minimumSize">
319
- <size>
320
- <width>125</width>
321
- <height>0</height>
322
- </size>
323
- </property>
324
- <property name="text">
325
- <string>Download Files</string>
326
- </property>
327
- </widget>
328
- </item>
329
- <item row="2" column="0">
330
- <widget class="QLabel" name="label_7">
331
- <property name="toolTip">
332
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;These are the file types that you are choosing to download. GBFF = annotation file, FASTA/FNA = genomic sequence file.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
333
- </property>
334
- <property name="text">
335
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;*&lt;/span&gt; File Types:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
336
- </property>
337
- </widget>
338
- </item>
339
- <item row="1" column="2">
340
- <widget class="QRadioButton" name="genbank_checkbox">
341
- <property name="text">
342
- <string>GenBank</string>
343
- </property>
344
- <property name="checked">
345
- <bool>false</bool>
346
- </property>
347
- </widget>
348
- </item>
349
- <item row="1" column="1">
350
- <widget class="QRadioButton" name="refseq_checkbox">
351
- <property name="text">
352
- <string>RefSeq</string>
353
- </property>
354
- <property name="checked">
355
- <bool>true</bool>
356
- </property>
357
- </widget>
358
- </item>
359
- <item row="0" column="0" colspan="3">
360
- <spacer name="verticalSpacer_7">
361
- <property name="orientation">
362
- <enum>Qt::Vertical</enum>
363
- </property>
364
- <property name="sizeType">
365
- <enum>QSizePolicy::Fixed</enum>
366
- </property>
367
- <property name="sizeHint" stdset="0">
368
- <size>
369
- <width>10</width>
370
- <height>10</height>
371
- </size>
372
- </property>
373
- </spacer>
374
- </item>
375
- <item row="1" column="0">
376
- <widget class="QLabel" name="label_6">
377
- <property name="toolTip">
378
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;These are the databases that files are downloaded from. Refseq is considered the gold standard for annotations and assemblies. It is HIGHLY recommended to use Refseq if available for the genome of interest.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
379
- </property>
380
- <property name="text">
381
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#ff0000;&quot;&gt;*&lt;/span&gt; Collections:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
382
- </property>
383
- </widget>
384
- </item>
385
- <item row="3" column="1" colspan="2">
386
- <widget class="QLabel" name="progressLabel">
387
- <property name="text">
388
- <string/>
389
- </property>
390
- </widget>
391
- </item>
392
- <item row="4" column="0" colspan="3">
393
- <widget class="QProgressBar" name="progressBar">
394
- <property name="value">
395
- <number>24</number>
396
- </property>
397
- </widget>
398
- </item>
399
- <item row="6" column="0" colspan="3">
400
- <layout class="QFormLayout" name="formLayout">
401
- <property name="fieldGrowthPolicy">
402
- <enum>QFormLayout::ExpandingFieldsGrow</enum>
403
- </property>
404
- </layout>
405
- </item>
406
- <item row="2" column="1">
407
- <widget class="QCheckBox" name="fna_checkbox">
408
- <property name="text">
409
- <string>FASTA/FNA</string>
410
- </property>
411
- <property name="checked">
412
- <bool>true</bool>
413
- </property>
414
- </widget>
415
- </item>
416
- <item row="2" column="2">
417
- <widget class="QCheckBox" name="gbff_checkbox">
418
- <property name="text">
419
- <string>GBFF</string>
420
- </property>
421
- <property name="checked">
422
- <bool>true</bool>
423
- </property>
424
- </widget>
425
- </item>
426
- </layout>
427
- </widget>
428
- </item>
429
- <item row="4" column="0" alignment="Qt::AlignLeft">
430
- <widget class="QPushButton" name="back_button">
431
- <property name="minimumSize">
432
- <size>
433
- <width>125</width>
434
- <height>0</height>
435
- </size>
436
- </property>
437
- <property name="text">
438
- <string>Back</string>
439
- </property>
440
- </widget>
441
- </item>
442
- </layout>
443
- </item>
444
- <item row="1" column="2">
445
- <spacer name="horizontalSpacer_2">
446
- <property name="orientation">
447
- <enum>Qt::Horizontal</enum>
448
- </property>
449
- <property name="sizeType">
450
- <enum>QSizePolicy::Fixed</enum>
451
- </property>
452
- <property name="sizeHint" stdset="0">
453
- <size>
454
- <width>20</width>
455
- <height>20</height>
456
- </size>
457
- </property>
458
- </spacer>
459
- </item>
460
- <item row="0" column="1">
461
- <spacer name="verticalSpacer_2">
462
- <property name="orientation">
463
- <enum>Qt::Vertical</enum>
464
- </property>
465
- <property name="sizeType">
466
- <enum>QSizePolicy::Fixed</enum>
467
- </property>
468
- <property name="sizeHint" stdset="0">
469
- <size>
470
- <width>20</width>
471
- <height>20</height>
472
- </size>
473
- </property>
474
- </spacer>
475
- </item>
476
- </layout>
477
- </widget>
478
- <widget class="QStatusBar" name="statusbar"/>
479
- </widget>
480
- <tabstops>
481
- <tabstop>organism_line_edit</tabstop>
482
- <tabstop>infra_name_line_edit</tabstop>
483
- <tabstop>ret_max_line_edit</tabstop>
484
- <tabstop>yes_box</tabstop>
485
- <tabstop>search_button</tabstop>
486
- <tabstop>all_rows</tabstop>
487
- <tabstop>ncbi_table</tabstop>
488
- <tabstop>genbank_checkbox</tabstop>
489
- <tabstop>refseq_checkbox</tabstop>
490
- <tabstop>download_button</tabstop>
491
- <tabstop>back_button</tabstop>
492
- </tabstops>
493
- <resources/>
494
- <connections/>
495
- </ui>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/ui/new_endonuclease_window.ui CHANGED
@@ -1,7 +1,7 @@
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <ui version="4.0">
3
  <class>MainWindow</class>
4
- <widget class="QMainWindow" name="MainWindow">
5
  <property name="geometry">
6
  <rect>
7
  <x>0</x>
 
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <ui version="4.0">
3
  <class>MainWindow</class>
4
+ <widget class="QWidget" name="MainWindow">
5
  <property name="geometry">
6
  <rect>
7
  <x>0</x>
src/ui/new_genome_window_old.ui DELETED
@@ -1,823 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <ui version="4.0">
3
- <class>MainWindow</class>
4
- <widget class="QMainWindow" name="MainWindow">
5
- <property name="geometry">
6
- <rect>
7
- <x>0</x>
8
- <y>0</y>
9
- <width>811</width>
10
- <height>862</height>
11
- </rect>
12
- </property>
13
- <property name="font">
14
- <font>
15
- <pointsize>12</pointsize>
16
- </font>
17
- </property>
18
- <property name="windowTitle">
19
- <string>MainWindow</string>
20
- </property>
21
- <widget class="QWidget" name="centralwidget">
22
- <layout class="QGridLayout" name="gridLayout_3">
23
- <item row="2" column="1" colspan="2">
24
- <widget class="QGroupBox" name="grpStep2">
25
- <property name="sizePolicy">
26
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
27
- <horstretch>0</horstretch>
28
- <verstretch>0</verstretch>
29
- </sizepolicy>
30
- </property>
31
- <property name="minimumSize">
32
- <size>
33
- <width>0</width>
34
- <height>0</height>
35
- </size>
36
- </property>
37
- <property name="maximumSize">
38
- <size>
39
- <width>16777215</width>
40
- <height>16777215</height>
41
- </size>
42
- </property>
43
- <property name="styleSheet">
44
- <string notr="true"/>
45
- </property>
46
- <property name="title">
47
- <string>Step 2: Select File and Add Job to Queue</string>
48
- </property>
49
- <layout class="QGridLayout" name="gridLayout_4">
50
- <property name="topMargin">
51
- <number>15</number>
52
- </property>
53
- <item row="0" column="0">
54
- <layout class="QVBoxLayout" name="verticalLayout_5">
55
- <item>
56
- <widget class="QLabel" name="lblSelectLocalFile">
57
- <property name="sizePolicy">
58
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
59
- <horstretch>0</horstretch>
60
- <verstretch>0</verstretch>
61
- </sizepolicy>
62
- </property>
63
- <property name="text">
64
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;* &lt;/span&gt;Select Local FASTA/FNA File:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
65
- </property>
66
- </widget>
67
- </item>
68
- <item>
69
- <layout class="QHBoxLayout" name="horizontalLayout">
70
- <item>
71
- <layout class="QHBoxLayout" name="horizontalLayout_10">
72
- <property name="sizeConstraint">
73
- <enum>QLayout::SetMinimumSize</enum>
74
- </property>
75
- <item>
76
- <widget class="QPushButton" name="pbtnNCBISearch">
77
- <property name="sizePolicy">
78
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
79
- <horstretch>0</horstretch>
80
- <verstretch>0</verstretch>
81
- </sizepolicy>
82
- </property>
83
- <property name="minimumSize">
84
- <size>
85
- <width>0</width>
86
- <height>0</height>
87
- </size>
88
- </property>
89
- <property name="text">
90
- <string>NCBI Search</string>
91
- </property>
92
- </widget>
93
- </item>
94
- <item>
95
- <widget class="QPushButton" name="pbtnBrowseFile">
96
- <property name="sizePolicy">
97
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
98
- <horstretch>0</horstretch>
99
- <verstretch>0</verstretch>
100
- </sizepolicy>
101
- </property>
102
- <property name="minimumSize">
103
- <size>
104
- <width>0</width>
105
- <height>0</height>
106
- </size>
107
- </property>
108
- <property name="text">
109
- <string>Browse File</string>
110
- </property>
111
- </widget>
112
- </item>
113
- </layout>
114
- </item>
115
- <item>
116
- <widget class="QLineEdit" name="ledSelectedFile">
117
- <property name="sizePolicy">
118
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
119
- <horstretch>0</horstretch>
120
- <verstretch>0</verstretch>
121
- </sizepolicy>
122
- </property>
123
- <property name="inputMask">
124
- <string/>
125
- </property>
126
- <property name="text">
127
- <string/>
128
- </property>
129
- <property name="readOnly">
130
- <bool>true</bool>
131
- </property>
132
- <property name="placeholderText">
133
- <string>Selected FASTA/FNA File</string>
134
- </property>
135
- </widget>
136
- </item>
137
- <item>
138
- <layout class="QHBoxLayout" name="horizontalLayout_9">
139
- <property name="sizeConstraint">
140
- <enum>QLayout::SetMinimumSize</enum>
141
- </property>
142
- </layout>
143
- </item>
144
- </layout>
145
- </item>
146
- <item>
147
- <layout class="QHBoxLayout" name="horizontalLayout_11">
148
- <item>
149
- <widget class="QPushButton" name="pbtnAddJob">
150
- <property name="sizePolicy">
151
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
152
- <horstretch>0</horstretch>
153
- <verstretch>0</verstretch>
154
- </sizepolicy>
155
- </property>
156
- <property name="minimumSize">
157
- <size>
158
- <width>0</width>
159
- <height>0</height>
160
- </size>
161
- </property>
162
- <property name="text">
163
- <string>Add Job to Queue</string>
164
- </property>
165
- </widget>
166
- </item>
167
- </layout>
168
- </item>
169
- </layout>
170
- </item>
171
- </layout>
172
- </widget>
173
- </item>
174
- <item row="1" column="1" colspan="2">
175
- <widget class="QGroupBox" name="grpStep1">
176
- <property name="sizePolicy">
177
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
178
- <horstretch>0</horstretch>
179
- <verstretch>0</verstretch>
180
- </sizepolicy>
181
- </property>
182
- <property name="minimumSize">
183
- <size>
184
- <width>0</width>
185
- <height>0</height>
186
- </size>
187
- </property>
188
- <property name="maximumSize">
189
- <size>
190
- <width>16777215</width>
191
- <height>16777215</height>
192
- </size>
193
- </property>
194
- <property name="font">
195
- <font>
196
- <family>Arial</family>
197
- <pointsize>12</pointsize>
198
- <weight>50</weight>
199
- <italic>false</italic>
200
- <bold>false</bold>
201
- </font>
202
- </property>
203
- <property name="styleSheet">
204
- <string notr="true"/>
205
- </property>
206
- <property name="title">
207
- <string>Step 1: Input Organism Info</string>
208
- </property>
209
- <layout class="QGridLayout" name="gridLayout_2">
210
- <property name="topMargin">
211
- <number>15</number>
212
- </property>
213
- <item row="3" column="4" colspan="2">
214
- <layout class="QHBoxLayout" name="chkBoxLayout">
215
- <item>
216
- <widget class="QCheckBox" name="chckGenerateRepeats">
217
- <property name="sizePolicy">
218
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
219
- <horstretch>0</horstretch>
220
- <verstretch>0</verstretch>
221
- </sizepolicy>
222
- </property>
223
- <property name="text">
224
- <string>Generate Repeats</string>
225
- </property>
226
- <property name="checked">
227
- <bool>true</bool>
228
- </property>
229
- </widget>
230
- </item>
231
- <item>
232
- <widget class="QCheckBox" name="chckMultithreading">
233
- <property name="sizePolicy">
234
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
235
- <horstretch>0</horstretch>
236
- <verstretch>0</verstretch>
237
- </sizepolicy>
238
- </property>
239
- <property name="text">
240
- <string>Multithreading</string>
241
- </property>
242
- <property name="checked">
243
- <bool>true</bool>
244
- </property>
245
- </widget>
246
- </item>
247
- </layout>
248
- </item>
249
- <item row="1" column="0">
250
- <widget class="QLabel" name="lblRequiredStrain">
251
- <property name="text">
252
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;*&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
253
- </property>
254
- </widget>
255
- </item>
256
- <item row="1" column="1">
257
- <widget class="QLabel" name="lblStrain">
258
- <property name="sizePolicy">
259
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
260
- <horstretch>0</horstretch>
261
- <verstretch>0</verstretch>
262
- </sizepolicy>
263
- </property>
264
- <property name="toolTip">
265
- <string/>
266
- </property>
267
- <property name="text">
268
- <string>Strain:</string>
269
- </property>
270
- </widget>
271
- </item>
272
- <item row="2" column="2">
273
- <widget class="QComboBox" name="cmbEndonuclease">
274
- <property name="sizePolicy">
275
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
276
- <horstretch>0</horstretch>
277
- <verstretch>0</verstretch>
278
- </sizepolicy>
279
- </property>
280
- <property name="maximumSize">
281
- <size>
282
- <width>16777215</width>
283
- <height>16777215</height>
284
- </size>
285
- </property>
286
- </widget>
287
- </item>
288
- <item row="3" column="1">
289
- <widget class="QLabel" name="lblOrganismCode">
290
- <property name="sizePolicy">
291
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
292
- <horstretch>0</horstretch>
293
- <verstretch>0</verstretch>
294
- </sizepolicy>
295
- </property>
296
- <property name="toolTip">
297
- <string>The organism code is the manner in which CASPER will label its data files
298
- and references for the organism you are importing here.</string>
299
- </property>
300
- <property name="text">
301
- <string>Organism Code:</string>
302
- </property>
303
- </widget>
304
- </item>
305
- <item row="0" column="1">
306
- <widget class="QLabel" name="lblOrganismName">
307
- <property name="sizePolicy">
308
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
309
- <horstretch>0</horstretch>
310
- <verstretch>0</verstretch>
311
- </sizepolicy>
312
- </property>
313
- <property name="text">
314
- <string>Organism Name:</string>
315
- </property>
316
- </widget>
317
- </item>
318
- <item row="2" column="4">
319
- <widget class="QLabel" name="lblThreeLength">
320
- <property name="sizePolicy">
321
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
322
- <horstretch>0</horstretch>
323
- <verstretch>0</verstretch>
324
- </sizepolicy>
325
- </property>
326
- <property name="toolTip">
327
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Length in nucleotides of gRNA sequence downstream (3')&lt;/p&gt;&lt;p&gt;of the seed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
328
- </property>
329
- <property name="text">
330
- <string>3' Length:</string>
331
- </property>
332
- </widget>
333
- </item>
334
- <item row="0" column="2">
335
- <widget class="QLineEdit" name="ledOrganismName">
336
- <property name="maximumSize">
337
- <size>
338
- <width>16777215</width>
339
- <height>16777215</height>
340
- </size>
341
- </property>
342
- </widget>
343
- </item>
344
- <item row="2" column="1">
345
- <widget class="QLabel" name="lblEndonuclease">
346
- <property name="sizePolicy">
347
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
348
- <horstretch>0</horstretch>
349
- <verstretch>0</verstretch>
350
- </sizepolicy>
351
- </property>
352
- <property name="toolTip">
353
- <string>The Cas endonuclease selected determines the gRNA
354
- search parameters</string>
355
- </property>
356
- <property name="text">
357
- <string>Endonuclease:</string>
358
- </property>
359
- </widget>
360
- </item>
361
- <item row="3" column="2">
362
- <widget class="QLineEdit" name="ledOrganismCode">
363
- <property name="maximumSize">
364
- <size>
365
- <width>16777215</width>
366
- <height>16777215</height>
367
- </size>
368
- </property>
369
- </widget>
370
- </item>
371
- <item row="1" column="4">
372
- <widget class="QLabel" name="lblFiveLength">
373
- <property name="sizePolicy">
374
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
375
- <horstretch>0</horstretch>
376
- <verstretch>0</verstretch>
377
- </sizepolicy>
378
- </property>
379
- <property name="toolTip">
380
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Length in nucleotides of gRNA sequence upstream (5')&lt;/p&gt;&lt;p&gt;of the seed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
381
- </property>
382
- <property name="text">
383
- <string>5' Length:</string>
384
- </property>
385
- </widget>
386
- </item>
387
- <item row="0" column="5">
388
- <widget class="QLineEdit" name="ledSeedLength">
389
- <property name="maximumSize">
390
- <size>
391
- <width>16777215</width>
392
- <height>16777215</height>
393
- </size>
394
- </property>
395
- <property name="readOnly">
396
- <bool>true</bool>
397
- </property>
398
- </widget>
399
- </item>
400
- <item row="1" column="5">
401
- <widget class="QLineEdit" name="ledFiveLength">
402
- <property name="maximumSize">
403
- <size>
404
- <width>16777215</width>
405
- <height>16777215</height>
406
- </size>
407
- </property>
408
- <property name="readOnly">
409
- <bool>true</bool>
410
- </property>
411
- </widget>
412
- </item>
413
- <item row="2" column="0">
414
- <widget class="QLabel" name="lblRequiredEndonuclease">
415
- <property name="text">
416
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;*&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
417
- </property>
418
- </widget>
419
- </item>
420
- <item row="0" column="3">
421
- <widget class="QLabel" name="lblRequiredSeedLength">
422
- <property name="text">
423
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;*&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
424
- </property>
425
- </widget>
426
- </item>
427
- <item row="2" column="5">
428
- <widget class="QLineEdit" name="ledThreeLength">
429
- <property name="maximumSize">
430
- <size>
431
- <width>16777215</width>
432
- <height>16777215</height>
433
- </size>
434
- </property>
435
- <property name="readOnly">
436
- <bool>true</bool>
437
- </property>
438
- </widget>
439
- </item>
440
- <item row="3" column="0">
441
- <widget class="QLabel" name="lblRequiredOrganismCode">
442
- <property name="text">
443
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;*&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
444
- </property>
445
- </widget>
446
- </item>
447
- <item row="1" column="2">
448
- <widget class="QLineEdit" name="ledStrain">
449
- <property name="maximumSize">
450
- <size>
451
- <width>16777215</width>
452
- <height>16777215</height>
453
- </size>
454
- </property>
455
- </widget>
456
- </item>
457
- <item row="0" column="4">
458
- <widget class="QLabel" name="lblSeedLength">
459
- <property name="sizePolicy">
460
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
461
- <horstretch>0</horstretch>
462
- <verstretch>0</verstretch>
463
- </sizepolicy>
464
- </property>
465
- <property name="toolTip">
466
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The length in nucleotides of the region of gRNA most crucial&lt;/p&gt;&lt;p&gt;for gRNA binding. CASPER uses this value to find repeated&lt;/p&gt;&lt;p&gt;gRNA sequences.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
467
- </property>
468
- <property name="text">
469
- <string>Seed Length:</string>
470
- </property>
471
- </widget>
472
- </item>
473
- <item row="0" column="0">
474
- <widget class="QLabel" name="lblRequiredOrganismName">
475
- <property name="text">
476
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;*&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
477
- </property>
478
- </widget>
479
- </item>
480
- </layout>
481
- </widget>
482
- </item>
483
- <item row="4" column="1" colspan="2">
484
- <layout class="QHBoxLayout" name="horizontalLayout_5">
485
- <item alignment="Qt::AlignRight">
486
- <widget class="QPushButton" name="pbtnResetForm">
487
- <property name="sizePolicy">
488
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
489
- <horstretch>0</horstretch>
490
- <verstretch>0</verstretch>
491
- </sizepolicy>
492
- </property>
493
- <property name="minimumSize">
494
- <size>
495
- <width>125</width>
496
- <height>0</height>
497
- </size>
498
- </property>
499
- <property name="maximumSize">
500
- <size>
501
- <width>16777215</width>
502
- <height>16777215</height>
503
- </size>
504
- </property>
505
- <property name="text">
506
- <string>Reset Form</string>
507
- </property>
508
- </widget>
509
- </item>
510
- </layout>
511
- </item>
512
- <item row="0" column="1">
513
- <widget class="QLabel" name="lblWindowHeading">
514
- <property name="minimumSize">
515
- <size>
516
- <width>0</width>
517
- <height>0</height>
518
- </size>
519
- </property>
520
- <property name="maximumSize">
521
- <size>
522
- <width>16777215</width>
523
- <height>50</height>
524
- </size>
525
- </property>
526
- <property name="font">
527
- <font>
528
- <family>Arial</family>
529
- <pointsize>12</pointsize>
530
- <weight>75</weight>
531
- <italic>false</italic>
532
- <bold>true</bold>
533
- </font>
534
- </property>
535
- <property name="styleSheet">
536
- <string notr="true"/>
537
- </property>
538
- <property name="text">
539
- <string>New Genome</string>
540
- </property>
541
- <property name="scaledContents">
542
- <bool>false</bool>
543
- </property>
544
- </widget>
545
- </item>
546
- <item row="3" column="1" colspan="2">
547
- <widget class="QGroupBox" name="Step3">
548
- <property name="sizePolicy">
549
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
550
- <horstretch>0</horstretch>
551
- <verstretch>0</verstretch>
552
- </sizepolicy>
553
- </property>
554
- <property name="minimumSize">
555
- <size>
556
- <width>0</width>
557
- <height>0</height>
558
- </size>
559
- </property>
560
- <property name="maximumSize">
561
- <size>
562
- <width>16777215</width>
563
- <height>16777215</height>
564
- </size>
565
- </property>
566
- <property name="font">
567
- <font>
568
- <family>Arial</family>
569
- <pointsize>12</pointsize>
570
- <weight>50</weight>
571
- <italic>false</italic>
572
- <bold>false</bold>
573
- </font>
574
- </property>
575
- <property name="styleSheet">
576
- <string notr="true"/>
577
- </property>
578
- <property name="title">
579
- <string>Step 3: Select and Run Jobs</string>
580
- </property>
581
- <layout class="QGridLayout" name="gridLayout_8">
582
- <property name="topMargin">
583
- <number>15</number>
584
- </property>
585
- <item row="0" column="0">
586
- <layout class="QHBoxLayout" name="horizontalLayout_6">
587
- <item>
588
- <layout class="QHBoxLayout" name="horizontalLayout_8">
589
- <item>
590
- <widget class="QPushButton" name="pbtnRemoveJob">
591
- <property name="sizePolicy">
592
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
593
- <horstretch>0</horstretch>
594
- <verstretch>0</verstretch>
595
- </sizepolicy>
596
- </property>
597
- <property name="minimumSize">
598
- <size>
599
- <width>0</width>
600
- <height>0</height>
601
- </size>
602
- </property>
603
- <property name="text">
604
- <string>Remove Job</string>
605
- </property>
606
- </widget>
607
- </item>
608
- <item>
609
- <widget class="QPushButton" name="pbtnRemoveAllJobs">
610
- <property name="sizePolicy">
611
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
612
- <horstretch>0</horstretch>
613
- <verstretch>0</verstretch>
614
- </sizepolicy>
615
- </property>
616
- <property name="minimumSize">
617
- <size>
618
- <width>0</width>
619
- <height>0</height>
620
- </size>
621
- </property>
622
- <property name="text">
623
- <string>Remove All Jobs</string>
624
- </property>
625
- </widget>
626
- </item>
627
- <item>
628
- <widget class="QPushButton" name="pbtnRunAllJobs">
629
- <property name="sizePolicy">
630
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
631
- <horstretch>0</horstretch>
632
- <verstretch>0</verstretch>
633
- </sizepolicy>
634
- </property>
635
- <property name="minimumSize">
636
- <size>
637
- <width>0</width>
638
- <height>0</height>
639
- </size>
640
- </property>
641
- <property name="text">
642
- <string>Run All Jobs</string>
643
- </property>
644
- </widget>
645
- </item>
646
- </layout>
647
- </item>
648
- <item>
649
- <layout class="QHBoxLayout" name="horizontalLayout_7">
650
- <item>
651
- <widget class="QLabel" name="lblProgressOutput">
652
- <property name="sizePolicy">
653
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
654
- <horstretch>0</horstretch>
655
- <verstretch>0</verstretch>
656
- </sizepolicy>
657
- </property>
658
- <property name="text">
659
- <string>Progress Output</string>
660
- </property>
661
- <property name="alignment">
662
- <set>Qt::AlignCenter</set>
663
- </property>
664
- </widget>
665
- </item>
666
- </layout>
667
- </item>
668
- </layout>
669
- </item>
670
- <item row="1" column="0">
671
- <layout class="QHBoxLayout" name="horizontalLayout_2">
672
- <item>
673
- <layout class="QVBoxLayout" name="verticalLayout_3">
674
- <item>
675
- <widget class="QTableWidget" name="tableJobs">
676
- <property name="sizePolicy">
677
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
678
- <horstretch>0</horstretch>
679
- <verstretch>0</verstretch>
680
- </sizepolicy>
681
- </property>
682
- <property name="minimumSize">
683
- <size>
684
- <width>0</width>
685
- <height>0</height>
686
- </size>
687
- </property>
688
- <property name="maximumSize">
689
- <size>
690
- <width>16777215</width>
691
- <height>16777215</height>
692
- </size>
693
- </property>
694
- <attribute name="horizontalHeaderDefaultSectionSize">
695
- <number>165</number>
696
- </attribute>
697
- <attribute name="horizontalHeaderMinimumSectionSize">
698
- <number>50</number>
699
- </attribute>
700
- <column>
701
- <property name="text">
702
- <string>Job Name</string>
703
- </property>
704
- </column>
705
- <column>
706
- <property name="text">
707
- <string>Job Status</string>
708
- </property>
709
- </column>
710
- </widget>
711
- </item>
712
- </layout>
713
- </item>
714
- <item>
715
- <layout class="QVBoxLayout" name="verticalLayout">
716
- <item>
717
- <layout class="QHBoxLayout" name="horizontalLayout_4">
718
- <property name="sizeConstraint">
719
- <enum>QLayout::SetDefaultConstraint</enum>
720
- </property>
721
- <item>
722
- <widget class="QTextBrowser" name="txtbrowserJobsProgressOutput">
723
- <property name="sizePolicy">
724
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
725
- <horstretch>0</horstretch>
726
- <verstretch>0</verstretch>
727
- </sizepolicy>
728
- </property>
729
- <property name="minimumSize">
730
- <size>
731
- <width>0</width>
732
- <height>0</height>
733
- </size>
734
- </property>
735
- <property name="verticalScrollBarPolicy">
736
- <enum>Qt::ScrollBarAsNeeded</enum>
737
- </property>
738
- <property name="horizontalScrollBarPolicy">
739
- <enum>Qt::ScrollBarAsNeeded</enum>
740
- </property>
741
- <property name="sizeAdjustPolicy">
742
- <enum>QAbstractScrollArea::AdjustIgnored</enum>
743
- </property>
744
- <property name="lineWrapMode">
745
- <enum>QTextEdit::NoWrap</enum>
746
- </property>
747
- </widget>
748
- </item>
749
- </layout>
750
- </item>
751
- <item>
752
- <widget class="QProgressBar" name="progbarJobs">
753
- <property name="minimumSize">
754
- <size>
755
- <width>0</width>
756
- <height>0</height>
757
- </size>
758
- </property>
759
- <property name="value">
760
- <number>0</number>
761
- </property>
762
- </widget>
763
- </item>
764
- </layout>
765
- </item>
766
- </layout>
767
- </item>
768
- </layout>
769
- </widget>
770
- </item>
771
- <item row="0" column="2" alignment="Qt::AlignRight">
772
- <widget class="QLabel" name="lblRequired">
773
- <property name="maximumSize">
774
- <size>
775
- <width>16777215</width>
776
- <height>33</height>
777
- </size>
778
- </property>
779
- <property name="text">
780
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#fc0107;&quot;&gt;* Required&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
781
- </property>
782
- </widget>
783
- </item>
784
- </layout>
785
- </widget>
786
- <action name="visit_repo">
787
- <property name="text">
788
- <string>Visit Repository</string>
789
- </property>
790
- </action>
791
- <action name="go_ncbi">
792
- <property name="text">
793
- <string>Go to NCBI</string>
794
- </property>
795
- </action>
796
- <action name="actionUpload_New_Endonuclease">
797
- <property name="text">
798
- <string>Define New Endonuclease</string>
799
- </property>
800
- </action>
801
- </widget>
802
- <tabstops>
803
- <tabstop>ledOrganismName</tabstop>
804
- <tabstop>ledStrain</tabstop>
805
- <tabstop>cmbEndonuclease</tabstop>
806
- <tabstop>ledOrganismCode</tabstop>
807
- <tabstop>ledSeedLength</tabstop>
808
- <tabstop>ledFiveLength</tabstop>
809
- <tabstop>ledThreeLength</tabstop>
810
- <tabstop>chckGenerateRepeats</tabstop>
811
- <tabstop>chckMultithreading</tabstop>
812
- <tabstop>pbtnNCBISearch</tabstop>
813
- <tabstop>pbtnBrowseFile</tabstop>
814
- <tabstop>ledSelectedFile</tabstop>
815
- <tabstop>pbtnRemoveJob</tabstop>
816
- <tabstop>pbtnRemoveAllJobs</tabstop>
817
- <tabstop>pbtnRunAllJobs</tabstop>
818
- <tabstop>tableJobs</tabstop>
819
- <tabstop>txtbrowserJobsProgressOutput</tabstop>
820
- </tabstops>
821
- <resources/>
822
- <connections/>
823
- </ui>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/ui/off_target.ui CHANGED
@@ -6,7 +6,7 @@
6
  <rect>
7
  <x>0</x>
8
  <y>0</y>
9
- <width>455</width>
10
  <height>656</height>
11
  </rect>
12
  </property>
@@ -20,97 +20,67 @@
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
- <item row="1" column="1">
24
  <layout class="QGridLayout" name="gridLayout">
25
  <item row="2" column="0" colspan="2">
26
- <widget class="QGroupBox" name="Step1">
27
  <property name="title">
28
- <string>Step 1: Select References</string>
29
  </property>
30
- <layout class="QGridLayout" name="gridLayout_4">
31
- <item row="2" column="1">
32
- <widget class="QComboBox" name="EndocomboBox">
33
- <property name="sizePolicy">
34
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
35
- <horstretch>0</horstretch>
36
- <verstretch>0</verstretch>
37
- </sizepolicy>
38
  </property>
39
  </widget>
40
  </item>
41
- <item row="1" column="0">
42
- <widget class="QLabel" name="label_2">
43
- <property name="toolTip">
44
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This is the reference organism that off-target will be ran against. List is populated based on currenet CSPR files in CASPER database directory.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
45
- </property>
46
  <property name="text">
47
- <string>Organism:</string>
48
  </property>
49
  </widget>
50
  </item>
51
- <item row="1" column="1">
52
- <widget class="QComboBox" name="OrgcomboBox">
53
- <property name="sizePolicy">
54
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
55
- <horstretch>0</horstretch>
56
- <verstretch>0</verstretch>
57
- </sizepolicy>
58
  </property>
59
  </widget>
60
  </item>
61
- <item row="0" column="0" colspan="2">
62
- <spacer name="verticalSpacer_3">
63
- <property name="orientation">
64
- <enum>Qt::Vertical</enum>
65
- </property>
66
- <property name="sizeType">
67
- <enum>QSizePolicy::Preferred</enum>
68
  </property>
69
- <property name="sizeHint" stdset="0">
70
- <size>
71
- <width>20</width>
72
- <height>10</height>
73
- </size>
74
  </property>
75
- </spacer>
76
  </item>
77
- <item row="2" column="0">
78
- <widget class="QLabel" name="label_3">
79
  <property name="toolTip">
80
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This is the reference endonuclease that will be used for off-target analysis.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
81
  </property>
82
  <property name="text">
83
- <string>Endonuclease:</string>
84
  </property>
85
  </widget>
86
  </item>
87
- <item row="3" column="0" colspan="2">
88
- <spacer name="verticalSpacer_7">
89
- <property name="orientation">
90
- <enum>Qt::Vertical</enum>
91
- </property>
92
- <property name="sizeType">
93
- <enum>QSizePolicy::Preferred</enum>
94
  </property>
95
- <property name="sizeHint" stdset="0">
96
- <size>
97
- <width>20</width>
98
- <height>10</height>
99
- </size>
100
  </property>
101
- </spacer>
102
  </item>
103
- </layout>
104
- </widget>
105
- </item>
106
- <item row="3" column="0" colspan="2">
107
- <widget class="QGroupBox" name="Step2">
108
- <property name="title">
109
- <string>Step 2: Set Parameters</string>
110
- </property>
111
- <layout class="QGridLayout" name="gridLayout_5">
112
- <item row="1" column="2">
113
- <widget class="QDoubleSpinBox" name="toleranceSpinBox">
114
  <property name="maximum">
115
  <double>0.500000000000000</double>
116
  </property>
@@ -122,104 +92,51 @@
122
  </property>
123
  </widget>
124
  </item>
125
- <item row="2" column="2">
126
- <widget class="QComboBox" name="mismatchcomboBox"/>
127
- </item>
128
- <item row="5" column="0">
129
- <widget class="QLabel" name="label">
130
  <property name="text">
131
- <string>Save Output File?</string>
132
- </property>
133
- </widget>
134
- </item>
135
- <item row="0" column="0" colspan="3">
136
- <spacer name="verticalSpacer_4">
137
- <property name="orientation">
138
- <enum>Qt::Vertical</enum>
139
- </property>
140
- <property name="sizeType">
141
- <enum>QSizePolicy::Preferred</enum>
142
- </property>
143
- <property name="sizeHint" stdset="0">
144
- <size>
145
- <width>20</width>
146
- <height>10</height>
147
- </size>
148
- </property>
149
- </spacer>
150
- </item>
151
- <item row="6" column="0" colspan="3">
152
- <spacer name="verticalSpacer_6">
153
- <property name="orientation">
154
- <enum>Qt::Vertical</enum>
155
- </property>
156
- <property name="sizeType">
157
- <enum>QSizePolicy::Preferred</enum>
158
- </property>
159
- <property name="sizeHint" stdset="0">
160
- <size>
161
- <width>20</width>
162
- <height>10</height>
163
- </size>
164
- </property>
165
- </spacer>
166
- </item>
167
- <item row="1" column="0">
168
- <widget class="QLabel" name="label_4">
169
- <property name="toolTip">
170
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Potential off-target sites scoring lower than this value are dismissed (I.e. they do not pose a threat for off-target activity). Computational time increases as tolerance value decreases.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
171
  </property>
172
- <property name="text">
173
- <string>Tolerance:</string>
174
  </property>
175
  </widget>
176
  </item>
177
- <item row="3" column="0">
178
- <widget class="QLabel" name="label_6">
179
- <property name="toolTip">
180
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check this option if you only want to see the average off-target score for each gRNA. Leaving this option unchecked will report the average off-target score as well as the putatitve off-target sequences, their locations, and individual off-target scores.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
181
- </property>
182
- <property name="text">
183
- <string>Average Output</string>
184
- </property>
185
- </widget>
186
  </item>
187
- <item row="3" column="2">
188
- <widget class="QCheckBox" name="AVG">
189
  <property name="text">
190
- <string/>
191
  </property>
192
  </widget>
193
  </item>
194
- <item row="2" column="0">
195
- <widget class="QLabel" name="label_5">
196
- <property name="toolTip">
197
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If the number of mismatches in a putative off-target site exceeds this number, CASPER will not score the site. Computational time increases as this number increases.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
198
- </property>
199
- <property name="text">
200
- <string>Max No. Mismatches:</string>
201
- </property>
202
- </widget>
203
- </item>
204
- <item row="5" column="1">
205
- <widget class="QCheckBox" name="outputCheckBox">
206
  <property name="text">
207
  <string>No</string>
208
  </property>
209
  </widget>
210
  </item>
211
- <item row="5" column="2">
212
- <widget class="QLineEdit" name="outputFileName">
213
- <property name="placeholderText">
214
- <string>Yes. Name the file.</string>
215
- </property>
216
- </widget>
217
- </item>
218
  </layout>
219
  </widget>
220
  </item>
221
- <item row="5" column="1" alignment="Qt::AlignRight">
222
- <widget class="QPushButton" name="submitButton">
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  <property name="minimumSize">
224
  <size>
225
  <width>125</width>
@@ -227,87 +144,77 @@
227
  </size>
228
  </property>
229
  <property name="text">
230
- <string>Submit</string>
231
  </property>
232
  </widget>
233
  </item>
234
  <item row="1" column="0" colspan="2">
235
- <widget class="Line" name="line">
236
- <property name="orientation">
237
- <enum>Qt::Horizontal</enum>
238
- </property>
239
- </widget>
240
- </item>
241
- <item row="4" column="0" colspan="2">
242
- <widget class="QGroupBox" name="Step3">
243
  <property name="title">
244
- <string>Step 3: Run Off-Target</string>
245
  </property>
246
- <layout class="QGridLayout" name="gridLayout_6">
247
- <item row="1" column="0">
248
- <widget class="QPushButton" name="Run">
 
 
 
249
  <property name="text">
250
- <string>Run</string>
251
  </property>
252
  </widget>
253
  </item>
254
- <item row="3" column="0">
255
- <widget class="QProgressBar" name="progressBar">
256
- <property name="value">
257
- <number>24</number>
 
 
 
258
  </property>
259
  </widget>
260
  </item>
261
- <item row="0" column="0">
262
- <spacer name="verticalSpacer_5">
263
- <property name="orientation">
264
- <enum>Qt::Vertical</enum>
265
- </property>
266
- <property name="sizeType">
267
- <enum>QSizePolicy::Preferred</enum>
268
  </property>
269
- <property name="sizeHint" stdset="0">
270
- <size>
271
- <width>20</width>
272
- <height>10</height>
273
- </size>
274
  </property>
275
- </spacer>
276
  </item>
277
- <item row="4" column="0">
278
- <spacer name="verticalSpacer_8">
279
- <property name="orientation">
280
- <enum>Qt::Vertical</enum>
281
- </property>
282
- <property name="sizeType">
283
- <enum>QSizePolicy::Preferred</enum>
284
- </property>
285
- <property name="sizeHint" stdset="0">
286
- <size>
287
- <width>20</width>
288
- <height>10</height>
289
- </size>
290
  </property>
291
- </spacer>
292
  </item>
293
  </layout>
294
  </widget>
295
  </item>
296
- <item row="0" column="0" colspan="2">
297
- <widget class="QLabel" name="title">
298
- <property name="sizePolicy">
299
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
300
- <horstretch>0</horstretch>
301
- <verstretch>0</verstretch>
302
- </sizepolicy>
303
- </property>
304
- <property name="text">
305
- <string>Off-Target Analysis</string>
306
  </property>
 
 
 
 
 
 
 
 
 
307
  </widget>
308
  </item>
309
- <item row="5" column="0" alignment="Qt::AlignLeft">
310
- <widget class="QPushButton" name="cancelButton">
311
  <property name="minimumSize">
312
  <size>
313
  <width>125</width>
@@ -315,79 +222,14 @@
315
  </size>
316
  </property>
317
  <property name="text">
318
- <string>Cancel</string>
319
  </property>
320
  </widget>
321
  </item>
322
  </layout>
323
  </item>
324
- <item row="2" column="1">
325
- <spacer name="verticalSpacer">
326
- <property name="orientation">
327
- <enum>Qt::Vertical</enum>
328
- </property>
329
- <property name="sizeType">
330
- <enum>QSizePolicy::Fixed</enum>
331
- </property>
332
- <property name="sizeHint" stdset="0">
333
- <size>
334
- <width>20</width>
335
- <height>20</height>
336
- </size>
337
- </property>
338
- </spacer>
339
- </item>
340
- <item row="1" column="2">
341
- <spacer name="horizontalSpacer_2">
342
- <property name="orientation">
343
- <enum>Qt::Horizontal</enum>
344
- </property>
345
- <property name="sizeType">
346
- <enum>QSizePolicy::Fixed</enum>
347
- </property>
348
- <property name="sizeHint" stdset="0">
349
- <size>
350
- <width>20</width>
351
- <height>20</height>
352
- </size>
353
- </property>
354
- </spacer>
355
- </item>
356
- <item row="1" column="0">
357
- <spacer name="horizontalSpacer">
358
- <property name="orientation">
359
- <enum>Qt::Horizontal</enum>
360
- </property>
361
- <property name="sizeType">
362
- <enum>QSizePolicy::Fixed</enum>
363
- </property>
364
- <property name="sizeHint" stdset="0">
365
- <size>
366
- <width>20</width>
367
- <height>20</height>
368
- </size>
369
- </property>
370
- </spacer>
371
- </item>
372
- <item row="0" column="1">
373
- <spacer name="verticalSpacer_2">
374
- <property name="orientation">
375
- <enum>Qt::Vertical</enum>
376
- </property>
377
- <property name="sizeType">
378
- <enum>QSizePolicy::Fixed</enum>
379
- </property>
380
- <property name="sizeHint" stdset="0">
381
- <size>
382
- <width>20</width>
383
- <height>20</height>
384
- </size>
385
- </property>
386
- </spacer>
387
- </item>
388
  </layout>
389
  </widget>
390
- <widget class="QStatusBar" name="statusbar"/>
391
  </widget>
392
  <resources/>
393
  <connections/>
 
6
  <rect>
7
  <x>0</x>
8
  <y>0</y>
9
+ <width>524</width>
10
  <height>656</height>
11
  </rect>
12
  </property>
 
20
  </property>
21
  <widget class="QWidget" name="centralwidget">
22
  <layout class="QGridLayout" name="gridLayout_2">
23
+ <item row="0" column="0">
24
  <layout class="QGridLayout" name="gridLayout">
25
  <item row="2" column="0" colspan="2">
26
+ <widget class="QGroupBox" name="grpStep2">
27
  <property name="title">
28
+ <string>Step 2: Set Parameters</string>
29
  </property>
30
+ <layout class="QGridLayout" name="gridLayout_5">
31
+ <item row="2" column="3">
32
+ <widget class="QRadioButton" name="rbtnAverageOutputNo">
33
+ <property name="text">
34
+ <string>No</string>
 
 
 
35
  </property>
36
  </widget>
37
  </item>
38
+ <item row="2" column="2">
39
+ <widget class="QRadioButton" name="rbtnAverageOutputYes">
 
 
 
40
  <property name="text">
41
+ <string>Yes</string>
42
  </property>
43
  </widget>
44
  </item>
45
+ <item row="4" column="0">
46
+ <widget class="QLabel" name="lblSaveOutputFile">
47
+ <property name="text">
48
+ <string>Save Output File?</string>
 
 
 
49
  </property>
50
  </widget>
51
  </item>
52
+ <item row="1" column="0">
53
+ <widget class="QLabel" name="lblMaxNoMismatches">
54
+ <property name="toolTip">
55
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If the number of mismatches in a putative off-target site exceeds this number, CASPER will not score the site. Computational time increases as this number increases.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
56
  </property>
57
+ <property name="text">
58
+ <string>Max No. Mismatches:</string>
 
 
 
59
  </property>
60
+ </widget>
61
  </item>
62
+ <item row="0" column="0">
63
+ <widget class="QLabel" name="lblTolerance">
64
  <property name="toolTip">
65
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Potential off-target sites scoring lower than this value are dismissed (I.e. they do not pose a threat for off-target activity). Computational time increases as tolerance value decreases.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
66
  </property>
67
  <property name="text">
68
+ <string>Tolerance:</string>
69
  </property>
70
  </widget>
71
  </item>
72
+ <item row="2" column="0">
73
+ <widget class="QLabel" name="lblAverageOutput">
74
+ <property name="toolTip">
75
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check this option if you only want to see the average off-target score for each gRNA. Leaving this option unchecked will report the average off-target score as well as the putatitve off-target sequences, their locations, and individual off-target scores.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
76
  </property>
77
+ <property name="text">
78
+ <string>Average Output</string>
 
 
 
79
  </property>
80
+ </widget>
81
  </item>
82
+ <item row="0" column="2" colspan="2">
83
+ <widget class="QDoubleSpinBox" name="dspnTolerance">
 
 
 
 
 
 
 
 
 
84
  <property name="maximum">
85
  <double>0.500000000000000</double>
86
  </property>
 
92
  </property>
93
  </widget>
94
  </item>
95
+ <item row="5" column="2" colspan="2">
96
+ <widget class="QLineEdit" name="ledSaveOutputFile">
 
 
 
97
  <property name="text">
98
+ <string/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  </property>
100
+ <property name="placeholderText">
101
+ <string>Please input the file name</string>
102
  </property>
103
  </widget>
104
  </item>
105
+ <item row="1" column="2" colspan="2">
106
+ <widget class="QComboBox" name="cmbMaxNoMismatches"/>
 
 
 
 
 
 
 
107
  </item>
108
+ <item row="4" column="2">
109
+ <widget class="QRadioButton" name="rbtnSaveOutputFileYes">
110
  <property name="text">
111
+ <string>Yes</string>
112
  </property>
113
  </widget>
114
  </item>
115
+ <item row="4" column="3">
116
+ <widget class="QRadioButton" name="rbtnSaveOutputFileNo">
 
 
 
 
 
 
 
 
 
 
117
  <property name="text">
118
  <string>No</string>
119
  </property>
120
  </widget>
121
  </item>
 
 
 
 
 
 
 
122
  </layout>
123
  </widget>
124
  </item>
125
+ <item row="0" column="0" colspan="2">
126
+ <widget class="QLabel" name="lblTitle">
127
+ <property name="sizePolicy">
128
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
129
+ <horstretch>0</horstretch>
130
+ <verstretch>0</verstretch>
131
+ </sizepolicy>
132
+ </property>
133
+ <property name="text">
134
+ <string>Off-Target Analysis</string>
135
+ </property>
136
+ </widget>
137
+ </item>
138
+ <item row="4" column="0" alignment="Qt::AlignLeft">
139
+ <widget class="QPushButton" name="pbtnCancel">
140
  <property name="minimumSize">
141
  <size>
142
  <width>125</width>
 
144
  </size>
145
  </property>
146
  <property name="text">
147
+ <string>Cancel</string>
148
  </property>
149
  </widget>
150
  </item>
151
  <item row="1" column="0" colspan="2">
152
+ <widget class="QGroupBox" name="grpStep1">
 
 
 
 
 
 
 
153
  <property name="title">
154
+ <string>Step 1: Select References</string>
155
  </property>
156
+ <layout class="QGridLayout" name="gridLayout_4">
157
+ <item row="0" column="0">
158
+ <widget class="QLabel" name="lblOrganism">
159
+ <property name="toolTip">
160
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This is the reference organism that off-target will be ran against. List is populated based on currenet CSPR files in CASPER database directory.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
161
+ </property>
162
  <property name="text">
163
+ <string>Organism:</string>
164
  </property>
165
  </widget>
166
  </item>
167
+ <item row="0" column="1">
168
+ <widget class="QComboBox" name="cmbOrganism">
169
+ <property name="sizePolicy">
170
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
171
+ <horstretch>0</horstretch>
172
+ <verstretch>0</verstretch>
173
+ </sizepolicy>
174
  </property>
175
  </widget>
176
  </item>
177
+ <item row="1" column="0">
178
+ <widget class="QLabel" name="lblEndonuclease">
179
+ <property name="toolTip">
180
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This is the reference endonuclease that will be used for off-target analysis.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
181
  </property>
182
+ <property name="text">
183
+ <string>Endonuclease:</string>
 
 
 
184
  </property>
185
+ </widget>
186
  </item>
187
+ <item row="1" column="1">
188
+ <widget class="QComboBox" name="cmbEndonuclease">
189
+ <property name="sizePolicy">
190
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
191
+ <horstretch>0</horstretch>
192
+ <verstretch>0</verstretch>
193
+ </sizepolicy>
 
 
 
 
 
 
194
  </property>
195
+ </widget>
196
  </item>
197
  </layout>
198
  </widget>
199
  </item>
200
+ <item row="3" column="0" colspan="2">
201
+ <widget class="QGroupBox" name="grpStep3">
202
+ <property name="title">
203
+ <string>Step 3: Run Off-Target</string>
 
 
 
 
 
 
204
  </property>
205
+ <layout class="QGridLayout" name="gridLayout_6">
206
+ <item row="1" column="0">
207
+ <widget class="QProgressBar" name="progBar">
208
+ <property name="value">
209
+ <number>24</number>
210
+ </property>
211
+ </widget>
212
+ </item>
213
+ </layout>
214
  </widget>
215
  </item>
216
+ <item row="4" column="1" alignment="Qt::AlignRight">
217
+ <widget class="QPushButton" name="pbtnSubmit">
218
  <property name="minimumSize">
219
  <size>
220
  <width>125</width>
 
222
  </size>
223
  </property>
224
  <property name="text">
225
+ <string>Submit</string>
226
  </property>
227
  </widget>
228
  </item>
229
  </layout>
230
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  </layout>
232
  </widget>
 
233
  </widget>
234
  <resources/>
235
  <connections/>
src/ui/population_analysis.ui CHANGED
@@ -143,7 +143,7 @@
143
  </widget>
144
  </item>
145
  <item row="3" column="1" alignment="Qt::AlignRight">
146
- <widget class="QPushButton" name="pbtnExportgRNA">
147
  <property name="minimumSize">
148
  <size>
149
  <width>175</width>
@@ -179,38 +179,6 @@
179
  <string>Select Organism(s) and Endonuclease:</string>
180
  </property>
181
  <layout class="QGridLayout" name="gridLayout_6">
182
- <item row="3" column="0" colspan="2">
183
- <widget class="QTabWidget" name="tabsSharedSeedHeatmap">
184
- <property name="sizePolicy">
185
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
186
- <horstretch>0</horstretch>
187
- <verstretch>0</verstretch>
188
- </sizepolicy>
189
- </property>
190
- <property name="minimumSize">
191
- <size>
192
- <width>0</width>
193
- <height>225</height>
194
- </size>
195
- </property>
196
- <property name="toolTip">
197
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Heatmap of shared repeats between all selected organisms. Diagonals show number of self-contained repeats. Axis labels correspond to the rows of the organism selection table.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
198
- </property>
199
- <property name="currentIndex">
200
- <number>0</number>
201
- </property>
202
- <widget class="QWidget" name="tabSharedSeedHeatmap">
203
- <attribute name="title">
204
- <string>Shared Seed Heatmap</string>
205
- </attribute>
206
- <layout class="QGridLayout" name="gridLayout_3">
207
- <item row="0" column="0">
208
- <widget class="QWidget" name="heatmapSeed" native="true"/>
209
- </item>
210
- </layout>
211
- </widget>
212
- </widget>
213
- </item>
214
  <item row="0" column="0">
215
  <widget class="QLabel" name="lblEndonuclease">
216
  <property name="sizePolicy">
@@ -240,45 +208,58 @@
240
  </property>
241
  </widget>
242
  </item>
 
 
 
 
 
 
 
 
 
 
243
  <item row="2" column="0" colspan="2">
244
  <widget class="QPushButton" name="pbtnAnalyzeOrganism">
245
- <property name="font">
246
- <font>
247
- <weight>75</weight>
248
- <bold>true</bold>
249
- </font>
250
- </property>
251
  <property name="text">
252
  <string>Analyze Organism(s)</string>
253
  </property>
254
  </widget>
255
  </item>
256
- <item row="0" column="1">
257
- <widget class="QComboBox" name="cmbEndonuclease">
258
  <property name="sizePolicy">
259
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
260
  <horstretch>0</horstretch>
261
  <verstretch>0</verstretch>
262
  </sizepolicy>
263
  </property>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  </widget>
265
  </item>
266
  </layout>
267
  </widget>
268
  </item>
269
- <item row="3" column="0" alignment="Qt::AlignLeft">
270
- <widget class="QPushButton" name="pbtnBack">
271
- <property name="minimumSize">
272
- <size>
273
- <width>125</width>
274
- <height>0</height>
275
- </size>
276
- </property>
277
- <property name="text">
278
- <string>Back</string>
279
- </property>
280
- </widget>
281
- </item>
282
  </layout>
283
  </item>
284
  </layout>
 
143
  </widget>
144
  </item>
145
  <item row="3" column="1" alignment="Qt::AlignRight">
146
+ <widget class="QPushButton" name="pbtnExportSelectedgRNAs">
147
  <property name="minimumSize">
148
  <size>
149
  <width>175</width>
 
179
  <string>Select Organism(s) and Endonuclease:</string>
180
  </property>
181
  <layout class="QGridLayout" name="gridLayout_6">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  <item row="0" column="0">
183
  <widget class="QLabel" name="lblEndonuclease">
184
  <property name="sizePolicy">
 
208
  </property>
209
  </widget>
210
  </item>
211
+ <item row="0" column="1">
212
+ <widget class="QComboBox" name="cmbEndonuclease">
213
+ <property name="sizePolicy">
214
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
215
+ <horstretch>0</horstretch>
216
+ <verstretch>0</verstretch>
217
+ </sizepolicy>
218
+ </property>
219
+ </widget>
220
+ </item>
221
  <item row="2" column="0" colspan="2">
222
  <widget class="QPushButton" name="pbtnAnalyzeOrganism">
 
 
 
 
 
 
223
  <property name="text">
224
  <string>Analyze Organism(s)</string>
225
  </property>
226
  </widget>
227
  </item>
228
+ <item row="3" column="0" colspan="2">
229
+ <widget class="QTabWidget" name="tabsSharedSeedHeatmap">
230
  <property name="sizePolicy">
231
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
232
  <horstretch>0</horstretch>
233
  <verstretch>0</verstretch>
234
  </sizepolicy>
235
  </property>
236
+ <property name="minimumSize">
237
+ <size>
238
+ <width>0</width>
239
+ <height>225</height>
240
+ </size>
241
+ </property>
242
+ <property name="toolTip">
243
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Heatmap of shared repeats between all selected organisms. Diagonals show number of self-contained repeats. Axis labels correspond to the rows of the organism selection table.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
244
+ </property>
245
+ <property name="currentIndex">
246
+ <number>0</number>
247
+ </property>
248
+ <widget class="QWidget" name="tabSharedSeedHeatmap">
249
+ <attribute name="title">
250
+ <string>Shared Seed Heatmap</string>
251
+ </attribute>
252
+ <layout class="QGridLayout" name="gridLayout_3">
253
+ <item row="0" column="0">
254
+ <widget class="QWidget" name="heatmapSeed" native="true"/>
255
+ </item>
256
+ </layout>
257
+ </widget>
258
  </widget>
259
  </item>
260
  </layout>
261
  </widget>
262
  </item>
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  </layout>
264
  </item>
265
  </layout>
src/ui/view_targets.ui CHANGED
@@ -6,7 +6,7 @@
6
  <rect>
7
  <x>0</x>
8
  <y>0</y>
9
- <width>1315</width>
10
  <height>916</height>
11
  </rect>
12
  </property>
@@ -22,255 +22,105 @@
22
  <layout class="QGridLayout" name="gridLayout_2">
23
  <item row="0" column="0">
24
  <layout class="QGridLayout" name="gridLayout">
25
- <item row="1" column="0">
26
- <widget class="QGroupBox" name="grpGuideAnalysis">
27
- <property name="title">
28
- <string>Guide Analysis</string>
29
- </property>
30
- <layout class="QGridLayout" name="gridLayout_5">
31
- <item row="0" column="1">
32
- <widget class="QPushButton" name="pbtnCoTargeting">
33
- <property name="sizePolicy">
34
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
35
- <horstretch>0</horstretch>
36
- <verstretch>0</verstretch>
37
- </sizepolicy>
38
- </property>
39
- <property name="minimumSize">
40
- <size>
41
- <width>125</width>
42
- <height>0</height>
43
- </size>
44
- </property>
45
- <property name="toolTip">
46
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Determine guides with synergistic PAMs (Ex. saCas9 and spCas9 compatible guides). &lt;span style=&quot; font-weight:600;&quot;&gt;Note:&lt;/span&gt; to analyze an organism for co-targeting guides, separate CSPR files must be generated for each additional endonuclease. Co-targeting endonucleases must have the same PAM directionality, same total gRNA length, and overlapping PAM sequences to be compatible.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
47
- </property>
48
- <property name="text">
49
- <string>Co-Targeting</string>
50
- </property>
51
- </widget>
52
- </item>
53
- <item row="0" column="0">
54
- <widget class="QPushButton" name="pbtnOffTarget">
55
- <property name="sizePolicy">
56
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
57
- <horstretch>0</horstretch>
58
- <verstretch>0</verstretch>
59
- </sizepolicy>
60
- </property>
61
- <property name="minimumSize">
62
- <size>
63
- <width>125</width>
64
- <height>0</height>
65
- </size>
66
- </property>
67
- <property name="toolTip">
68
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Perform off-target analysis on the selected guides.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
69
- </property>
70
- <property name="text">
71
- <string>Off-Target</string>
72
- </property>
73
- </widget>
74
- </item>
75
- </layout>
76
- </widget>
77
- </item>
78
- <item row="2" column="1" alignment="Qt::AlignRight">
79
- <widget class="QPushButton" name="pbtnExportgRNA">
80
- <property name="sizePolicy">
81
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
82
- <horstretch>0</horstretch>
83
- <verstretch>0</verstretch>
84
- </sizepolicy>
85
- </property>
86
- <property name="minimumSize">
87
- <size>
88
- <width>175</width>
89
- <height>0</height>
90
- </size>
91
- </property>
92
- <property name="toolTip">
93
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Export selected guides to a CSV file.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
94
- </property>
95
- <property name="text">
96
- <string>Export Selected gRNAs</string>
97
- </property>
98
- </widget>
99
- </item>
100
- <item row="0" column="0">
101
- <widget class="QGroupBox" name="grpGuideViewer">
102
  <property name="sizePolicy">
103
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
104
  <horstretch>0</horstretch>
105
  <verstretch>0</verstretch>
106
  </sizepolicy>
107
  </property>
108
- <property name="minimumSize">
109
- <size>
110
- <width>625</width>
111
- <height>0</height>
112
- </size>
113
- </property>
114
  <property name="title">
115
- <string>Guide Viewer</string>
116
  </property>
117
- <layout class="QGridLayout" name="gridLayout_4">
118
- <item row="4" column="0">
119
- <widget class="QLabel" name="lblGene">
120
- <property name="sizePolicy">
121
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
122
- <horstretch>0</horstretch>
123
- <verstretch>0</verstretch>
124
- </sizepolicy>
125
- </property>
126
- <property name="text">
127
- <string>Gene:</string>
128
- </property>
129
- </widget>
130
- </item>
131
- <item row="8" column="2">
132
- <widget class="QPushButton" name="pbtnScoringOptions">
133
  <property name="text">
134
- <string>Scoring Options</string>
135
  </property>
136
  </widget>
137
  </item>
138
- <item row="5" column="1" colspan="4">
139
- <layout class="QHBoxLayout" name="horizontalLayout_2">
140
- <item>
141
- <widget class="QComboBox" name="cmbEndonuclease">
142
- <property name="sizePolicy">
143
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
144
- <horstretch>0</horstretch>
145
- <verstretch>0</verstretch>
146
- </sizepolicy>
147
- </property>
148
- <property name="minimumSize">
149
- <size>
150
- <width>225</width>
151
- <height>0</height>
152
- </size>
153
- </property>
154
- </widget>
155
- </item>
156
- </layout>
157
  </item>
158
- <item row="8" column="1" alignment="Qt::AlignLeft">
159
- <widget class="QPushButton" name="pbtnFilterOptions">
160
- <property name="sizePolicy">
161
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
162
- <horstretch>0</horstretch>
163
- <verstretch>0</verstretch>
164
- </sizepolicy>
165
- </property>
166
- <property name="minimumSize">
167
- <size>
168
- <width>125</width>
169
- <height>0</height>
170
- </size>
171
- </property>
172
- <property name="toolTip">
173
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Additional options for filtering the Guide Viewer Table.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
174
- </property>
175
  <property name="text">
176
- <string>Filter Options</string>
177
  </property>
178
  </widget>
179
  </item>
180
- <item row="5" column="0">
181
- <widget class="QLabel" name="lblEndonuclease">
182
- <property name="sizePolicy">
183
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
184
- <horstretch>0</horstretch>
185
- <verstretch>0</verstretch>
186
- </sizepolicy>
187
- </property>
188
  <property name="text">
189
- <string>Endonuclease:</string>
190
  </property>
191
  </widget>
192
  </item>
193
- <item row="9" column="0" colspan="5">
194
- <widget class="QTableWidget" name="tblTargets"/>
195
  </item>
196
- <item row="8" column="0">
197
- <widget class="QCheckBox" name="chkSelectAll">
198
- <property name="sizePolicy">
199
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
200
- <horstretch>0</horstretch>
201
- <verstretch>0</verstretch>
202
- </sizepolicy>
203
  </property>
204
  <property name="text">
205
- <string>Select All</string>
206
  </property>
207
  </widget>
208
  </item>
209
- <item row="4" column="1" colspan="4">
210
- <layout class="QHBoxLayout" name="horizontalLayout">
211
- <item>
212
- <widget class="QComboBox" name="cmbGene">
213
- <property name="sizePolicy">
214
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
215
- <horstretch>0</horstretch>
216
- <verstretch>0</verstretch>
217
- </sizepolicy>
218
- </property>
219
- <property name="minimumSize">
220
- <size>
221
- <width>225</width>
222
- <height>0</height>
223
- </size>
224
- </property>
225
- </widget>
226
- </item>
227
- </layout>
228
  </item>
229
  </layout>
230
  </widget>
231
  </item>
232
- <item row="0" column="1" rowspan="2">
233
- <widget class="QGroupBox" name="grpGeneViewer">
234
  <property name="sizePolicy">
235
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
236
  <horstretch>0</horstretch>
237
  <verstretch>0</verstretch>
238
  </sizepolicy>
239
  </property>
240
- <property name="minimumSize">
241
- <size>
242
- <width>0</width>
243
- <height>0</height>
244
- </size>
245
- </property>
246
  <property name="title">
247
- <string>Gene Viewer</string>
248
  </property>
249
- <layout class="QGridLayout" name="gridLayout_6">
250
- <item row="2" column="3">
251
- <widget class="QLineEdit" name="ledStopLocation">
252
- <property name="sizePolicy">
253
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
254
- <horstretch>0</horstretch>
255
- <verstretch>0</verstretch>
256
- </sizepolicy>
257
  </property>
258
  </widget>
259
  </item>
260
- <item row="0" column="3">
261
- <widget class="QPushButton" name="pbtnClearGuides">
262
- <property name="sizePolicy">
263
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
264
- <horstretch>0</horstretch>
265
- <verstretch>0</verstretch>
266
- </sizepolicy>
267
  </property>
 
 
 
 
268
  <property name="minimumSize">
269
  <size>
270
- <width>0</width>
271
  <height>0</height>
272
  </size>
273
  </property>
 
 
 
 
274
  <property name="toolTip">
275
  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This button clears all highlighted guides from the gene viewer box.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
276
  </property>
@@ -279,101 +129,81 @@
279
  </property>
280
  </widget>
281
  </item>
282
- <item row="2" column="0">
283
- <widget class="QLabel" name="lblStartLocation">
284
- <property name="sizePolicy">
285
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
286
- <horstretch>0</horstretch>
287
- <verstretch>0</verstretch>
288
- </sizepolicy>
289
- </property>
290
  <property name="text">
291
- <string>Start Location:</string>
292
  </property>
293
  </widget>
294
  </item>
295
- <item row="2" column="2">
296
- <widget class="QLabel" name="lblStopLocation">
297
- <property name="sizePolicy">
298
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
299
- <horstretch>0</horstretch>
300
- <verstretch>0</verstretch>
301
- </sizepolicy>
302
- </property>
303
  <property name="text">
304
- <string>Stop Location:</string>
305
  </property>
306
  </widget>
307
  </item>
308
- <item row="4" column="0" colspan="5">
309
- <widget class="QTextEdit" name="txtedGeneViewer">
310
- <property name="sizePolicy">
311
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
312
- <horstretch>0</horstretch>
313
- <verstretch>0</verstretch>
314
- </sizepolicy>
315
  </property>
316
  </widget>
317
  </item>
318
- <item row="0" column="1">
319
- <widget class="QPushButton" name="pbtnHighlightGuides">
320
- <property name="sizePolicy">
321
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
322
- <horstretch>0</horstretch>
323
- <verstretch>0</verstretch>
324
- </sizepolicy>
325
- </property>
326
- <property name="minimumSize">
327
- <size>
328
- <width>0</width>
329
- <height>0</height>
330
- </size>
331
- </property>
332
- <property name="toolTip">
333
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This button will highlight the sequences in the Gene Viewer that match the sequences selected in the table.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
334
- </property>
335
  <property name="text">
336
- <string>Highlight Guides</string>
337
  </property>
338
  </widget>
339
  </item>
340
- <item row="2" column="1">
341
- <widget class="QLineEdit" name="ledStartLocation">
342
- <property name="sizePolicy">
343
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
344
- <horstretch>0</horstretch>
345
- <verstretch>0</verstretch>
346
- </sizepolicy>
347
  </property>
348
  </widget>
349
  </item>
350
- <item row="0" column="4">
351
- <widget class="QCheckBox" name="displayGeneViewer">
352
  <property name="text">
353
- <string>Display On</string>
354
  </property>
355
  </widget>
356
  </item>
357
- <item row="2" column="4">
358
- <widget class="QPushButton" name="pbtnChangeLocation">
359
- <property name="minimumSize">
360
- <size>
361
- <width>0</width>
362
- <height>0</height>
363
- </size>
364
  </property>
 
 
 
 
 
 
 
365
  <property name="toolTip">
366
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This button changes the start and end location of the Gene Viewer sequence, based on what is entered in the Start and Stop boxes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
367
  </property>
368
  <property name="text">
369
- <string>Change Location</string>
370
  </property>
371
  </widget>
372
  </item>
373
- <item row="5" column="4">
374
- <widget class="QPushButton" name="pbtnResetLocation">
 
 
 
375
  <property name="text">
376
- <string>Reset Location</string>
377
  </property>
378
  </widget>
379
  </item>
 
6
  <rect>
7
  <x>0</x>
8
  <y>0</y>
9
+ <width>1335</width>
10
  <height>916</height>
11
  </rect>
12
  </property>
 
22
  <layout class="QGridLayout" name="gridLayout_2">
23
  <item row="0" column="0">
24
  <layout class="QGridLayout" name="gridLayout">
25
+ <item row="0" column="1" rowspan="2">
26
+ <widget class="QGroupBox" name="grpGeneViewer">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  <property name="sizePolicy">
28
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
29
  <horstretch>0</horstretch>
30
  <verstretch>0</verstretch>
31
  </sizepolicy>
32
  </property>
 
 
 
 
 
 
33
  <property name="title">
34
+ <string>Gene Viewer</string>
35
  </property>
36
+ <layout class="QGridLayout" name="gridLayout_6">
37
+ <item row="2" column="0">
38
+ <widget class="QLabel" name="lblStartLocation">
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  <property name="text">
40
+ <string>Start:</string>
41
  </property>
42
  </widget>
43
  </item>
44
+ <item row="2" column="1">
45
+ <widget class="QLineEdit" name="ledStartLocation"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  </item>
47
+ <item row="3" column="0">
48
+ <widget class="QLabel" name="lblStopLocation">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  <property name="text">
50
+ <string>Stop:</string>
51
  </property>
52
  </widget>
53
  </item>
54
+ <item row="3" column="2">
55
+ <widget class="QPushButton" name="pbtnResetLocation">
 
 
 
 
 
 
56
  <property name="text">
57
+ <string>Reset Location</string>
58
  </property>
59
  </widget>
60
  </item>
61
+ <item row="3" column="1">
62
+ <widget class="QLineEdit" name="ledStopLocation"/>
63
  </item>
64
+ <item row="2" column="2">
65
+ <widget class="QPushButton" name="pbtnChangeLocation">
66
+ <property name="toolTip">
67
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This button changes the start and end location of the Gene Viewer sequence, based on what is entered in the Start and Stop boxes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
68
  </property>
69
  <property name="text">
70
+ <string>Change Location</string>
71
  </property>
72
  </widget>
73
  </item>
74
+ <item row="5" column="0" colspan="5">
75
+ <widget class="QTextEdit" name="txtedGeneViewer"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  </item>
77
  </layout>
78
  </widget>
79
  </item>
80
+ <item row="0" column="0">
81
+ <widget class="QGroupBox" name="grpGuideViewer">
82
  <property name="sizePolicy">
83
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
84
  <horstretch>0</horstretch>
85
  <verstretch>0</verstretch>
86
  </sizepolicy>
87
  </property>
 
 
 
 
 
 
88
  <property name="title">
89
+ <string>Guide Viewer</string>
90
  </property>
91
+ <layout class="QGridLayout" name="gridLayout_4">
92
+ <item row="4" column="1" colspan="3">
93
+ <widget class="QComboBox" name="cmbGene">
94
+ <property name="minimumSize">
95
+ <size>
96
+ <width>225</width>
97
+ <height>0</height>
98
+ </size>
99
  </property>
100
  </widget>
101
  </item>
102
+ <item row="10" column="2">
103
+ <widget class="QPushButton" name="pbtnHighlightGuides">
104
+ <property name="toolTip">
105
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This button will highlight the sequences in the Gene Viewer that match the sequences selected in the table.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
106
+ </property>
107
+ <property name="text">
108
+ <string>Highlight Guides</string>
109
  </property>
110
+ </widget>
111
+ </item>
112
+ <item row="5" column="1" colspan="3">
113
+ <widget class="QComboBox" name="cmbEndonuclease">
114
  <property name="minimumSize">
115
  <size>
116
+ <width>225</width>
117
  <height>0</height>
118
  </size>
119
  </property>
120
+ </widget>
121
+ </item>
122
+ <item row="10" column="3">
123
+ <widget class="QPushButton" name="pbtnClearGuides">
124
  <property name="toolTip">
125
  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;This button clears all highlighted guides from the gene viewer box.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
126
  </property>
 
129
  </property>
130
  </widget>
131
  </item>
132
+ <item row="6" column="2">
133
+ <widget class="QLabel" name="lblMinOTScore">
 
 
 
 
 
 
134
  <property name="text">
135
+ <string>Minimum On-Target Score</string>
136
  </property>
137
  </widget>
138
  </item>
139
+ <item row="11" column="0" colspan="4">
140
+ <widget class="QTableWidget" name="tblGuides"/>
141
+ </item>
142
+ <item row="6" column="3">
143
+ <widget class="QSpinBox" name="spnMinOTScore"/>
144
+ </item>
145
+ <item row="5" column="0">
146
+ <widget class="QLabel" name="lblEndonuclease">
147
  <property name="text">
148
+ <string>Endonuclease:</string>
149
  </property>
150
  </widget>
151
  </item>
152
+ <item row="12" column="0">
153
+ <widget class="QPushButton" name="pbtnScoringOptions">
154
+ <property name="text">
155
+ <string>Scoring Options</string>
 
 
 
156
  </property>
157
  </widget>
158
  </item>
159
+ <item row="6" column="0">
160
+ <widget class="QCheckBox" name="chkFilter5PrimeG">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  <property name="text">
162
+ <string>Filter 5' G Sequences</string>
163
  </property>
164
  </widget>
165
  </item>
166
+ <item row="10" column="0">
167
+ <widget class="QCheckBox" name="chkSelectAll">
168
+ <property name="text">
169
+ <string>Select All</string>
 
 
 
170
  </property>
171
  </widget>
172
  </item>
173
+ <item row="4" column="0">
174
+ <widget class="QLabel" name="lblGene">
175
  <property name="text">
176
+ <string>Gene:</string>
177
  </property>
178
  </widget>
179
  </item>
180
+ <item row="12" column="1">
181
+ <widget class="QPushButton" name="pbtnOffTarget">
182
+ <property name="toolTip">
183
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Perform off-target analysis on the selected guides.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
 
 
184
  </property>
185
+ <property name="text">
186
+ <string>Off-Target</string>
187
+ </property>
188
+ </widget>
189
+ </item>
190
+ <item row="12" column="2">
191
+ <widget class="QPushButton" name="pbtnCoTargeting">
192
  <property name="toolTip">
193
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Determine guides with synergistic PAMs (Ex. saCas9 and spCas9 compatible guides). &lt;span style=&quot; font-weight:600;&quot;&gt;Note:&lt;/span&gt; to analyze an organism for co-targeting guides, separate CSPR files must be generated for each additional endonuclease. Co-targeting endonucleases must have the same PAM directionality, same total gRNA length, and overlapping PAM sequences to be compatible.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
194
  </property>
195
  <property name="text">
196
+ <string>Co-Targeting</string>
197
  </property>
198
  </widget>
199
  </item>
200
+ <item row="12" column="3">
201
+ <widget class="QPushButton" name="pbtnExportSelectedgRNAs">
202
+ <property name="toolTip">
203
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Export selected guides to a CSV file.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
204
+ </property>
205
  <property name="text">
206
+ <string>Export Selected gRNAs</string>
207
  </property>
208
  </widget>
209
  </item>
src/utils/ui.py CHANGED
@@ -1,7 +1,7 @@
1
- from PyQt6 import QtWidgets, QtGui, QtCore
2
  import traceback
3
- import models.GlobalSettings as GlobalSettings
4
-
5
 
6
  def show_message(title, message, fontSize=12, icon=QtWidgets.QMessageBox.Icon.Information, button=QtWidgets.QMessageBox.StandardButton.Close):
7
  try:
@@ -15,20 +15,27 @@ def show_message(title, message, fontSize=12, icon=QtWidgets.QMessageBox.Icon.In
15
  except Exception as e:
16
  print(f"Error showing message: {e}")
17
 
18
- def show_error(settings, message, e):
19
- try:
20
- logger = settings.get_logger()
 
 
21
  logger.critical(message)
22
- logger.critical(e)
23
- logger.critical(traceback.format_exc())
24
-
25
- show_message(
26
- fontSize=12,
27
- icon=QtWidgets.QMessageBox.Icon.Critical,
28
- title="Fatal Error",
29
- message=f"Fatal Error:\n{str(e)}\n\nFor more information on this error, look at CASPER.log in the application folder."
30
- )
31
- except Exception as e:
32
- print(f"Error showing error message: {e}")
33
 
34
- exit(-1)
 
 
 
 
 
 
 
 
 
 
 
1
+ from PyQt6 import QtWidgets
2
  import traceback
3
+ import sys
4
+ from PyQt6.QtWidgets import QMessageBox
5
 
6
  def show_message(title, message, fontSize=12, icon=QtWidgets.QMessageBox.Icon.Information, button=QtWidgets.QMessageBox.StandardButton.Close):
7
  try:
 
15
  except Exception as e:
16
  print(f"Error showing message: {e}")
17
 
18
+ def show_error(global_settings, message, exception=None):
19
+ """Show error dialog and log the error"""
20
+ logger = global_settings.get_logger() if global_settings else None
21
+
22
+ if logger:
23
  logger.critical(message)
24
+ if exception:
25
+ if isinstance(exception, str):
26
+ logger.critical(exception)
27
+ else:
28
+ logger.critical(str(exception))
29
+ logger.critical(''.join(traceback.format_tb(exception.__traceback__)))
 
 
 
 
 
30
 
31
+ error_box = QMessageBox()
32
+ error_box.setIcon(QMessageBox.Icon.Critical)
33
+ error_box.setText(message)
34
+ if exception:
35
+ if isinstance(exception, str):
36
+ error_box.setDetailedText(exception)
37
+ else:
38
+ error_box.setDetailedText(f"{str(exception)}\n\n{''.join(traceback.format_tb(exception.__traceback__))}")
39
+ error_box.exec()
40
+
41
+ sys.exit(1)
src/views/CoTargetingView.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from PyQt6 import QtWidgets, uic
3
+ from PyQt6.QtWidgets import QTableWidgetItem, QAbstractItemView, QMessageBox
4
+ from utils.ui import show_error
5
+
6
+ class CoTargetingView(QtWidgets.QMainWindow):
7
+ def __init__(self, global_settings):
8
+ super().__init__()
9
+ self.settings = global_settings
10
+ self.logger = global_settings.get_logger()
11
+ self.init_ui()
12
+
13
+ def init_ui(self):
14
+ try:
15
+ uic.loadUi(self.settings.get_ui_dir_path() + '/cotargeting.ui', self)
16
+ self.setWindowTitle("Co-targeting")
17
+
18
+
19
+ self.line_edit_organism = self._find_widget('ledOrganism', QtWidgets.QLineEdit)
20
+ self.table_endonucleases = self._find_widget('tblEndonucleases', QtWidgets.QTableWidget)
21
+
22
+ self.push_button_cancel = self._find_widget('pbtnCancel', QtWidgets.QPushButton)
23
+ self.push_button_submit = self._find_widget('pbtnSubmit', QtWidgets.QPushButton)
24
+
25
+ # Initialize table
26
+ self.table_endonucleases.setColumnCount(1)
27
+ self.table_endonucleases.setShowGrid(True)
28
+ self.table_endonucleases.setHorizontalHeaderLabels(["Endonuclease"])
29
+ self.table_endonucleases.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
30
+ self.table_endonucleases.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
31
+ self.table_endonucleases.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
32
+ self.table_endonucleases.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch)
33
+
34
+ except Exception as e:
35
+ show_error(self.settings, "Error initializing CoTargeting UI", str(e))
36
+
37
+ def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
38
+ widget = self.findChild(widget_type, name)
39
+ if widget is None:
40
+ self.logger.warning(f"Widget '{name}' not found in UI file.")
41
+ return widget
42
+
43
+ def populate_table(self, endo_choices):
44
+ """Populate table with endonuclease choices"""
45
+ try:
46
+ # Filter original endonucleases (no co-targeted ones)
47
+ filtered_endos = [item for item in endo_choices
48
+ if len(item.split(",")) == 1 and "|" not in item]
49
+
50
+ self.table_endonucleases.setRowCount(len(filtered_endos))
51
+ for i, endo in enumerate(filtered_endos):
52
+ self.table_endonucleases.setItem(i, 0, QTableWidgetItem(endo))
53
+
54
+ self.table_endonucleases.resizeColumnsToContents()
55
+
56
+ except Exception as e:
57
+ show_error(self.settings, "Error populating table", str(e))
58
+
59
+ def get_selected_endonucleases(self):
60
+ """Get list of selected endonucleases"""
61
+ try:
62
+ selected = []
63
+ for item in self.table_endonucleases.selectedItems():
64
+ selected.append(item.text())
65
+ return selected
66
+ except Exception as e:
67
+ self.logger.error(f"Error getting selected endonucleases: {str(e)}")
68
+ return []
69
+
70
+ def show_error(self, title, message):
71
+ """Show error message"""
72
+ QMessageBox.critical(self, title, message)
73
+
src/views/ExportSelectedgRNAsView.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from PyQt6.QtWidgets import QMainWindow
3
+ from PyQt6 import uic, QtWidgets
4
+ import os
5
+
6
+ class ExportSelectedgRNAsView(QMainWindow):
7
+ def __init__(self, global_settings):
8
+ super().__init__()
9
+ self.settings = global_settings
10
+ self.logger = self.settings.get_logger()
11
+ self._init_ui()
12
+
13
+ def _init_ui(self) -> None:
14
+ try:
15
+ uic.loadUi(self.settings.get_ui_dir_path() + '/export_selected_gRNAs.ui', self)
16
+ self.setWindowTitle("Export Selected gRNAs")
17
+ self._init_ui_components()
18
+ except Exception as e:
19
+ self.logger.error(f"Error initializing ExportSelectedgRNAsView: {str(e)}", exc_info=True)
20
+ raise
21
+
22
+ def _init_ui_components(self) -> None:
23
+ self._init_grpExportSettings()
24
+ self._init_grpGuideOptions()
25
+
26
+ self.push_button_cancel = self._find_widget('pbtnCancel', QtWidgets.QPushButton)
27
+ self.push_button_export = self._find_widget('pbtnExport', QtWidgets.QPushButton)
28
+
29
+ def _init_grpExportSettings(self) -> None:
30
+ self.line_edit_file_path = self._find_widget('ledFilePath', QtWidgets.QLineEdit)
31
+ self.push_button_browse = self._find_widget('pbtnBrowse', QtWidgets.QPushButton)
32
+ self.line_edit_file_name = self._find_widget('ledFileName', QtWidgets.QLineEdit)
33
+ self.combo_box_delimiter = self._find_widget('cmbDelimiter', QtWidgets.QComboBox)
34
+
35
+ def _init_grpGuideOptions(self) -> None:
36
+ self.line_edit_leading_sequence = self._find_widget('ledLeadingsequence', QtWidgets.QLineEdit)
37
+ self.line_edit_trailing_sequence = self._find_widget('ledTrailingSequence', QtWidgets.QLineEdit)
38
+
39
+ def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
40
+ widget = self.findChild(widget_type, name)
41
+ if widget is None:
42
+ self.logger.warning(f"Widget '{name}' not found in UI file.")
43
+ return widget
44
+
45
+ def show_dialog(self) -> None:
46
+ self.show()
47
+ self.activateWindow()
48
+
49
+ def get_export_settings(self) -> dict:
50
+ return {
51
+ 'file_path': self.line_edit_file_path.text(),
52
+ 'file_name': self.line_edit_file_name.text(),
53
+ 'delimiter': self.combo_box_delimiter.currentText(),
54
+ 'leading_sequence': self.line_edit_leading_sequence.text().strip(),
55
+ 'trailing_sequence': self.line_edit_trailing_sequence.text().strip()
56
+ }
57
+
58
+ def set_file_path(self, path: str) -> None:
59
+ self.line_edit_file_path.setText(path)
src/views/FindTargetsView.py CHANGED
@@ -27,30 +27,29 @@ class FindTargetsView(QtWidgets.QMainWindow):
27
  self.results_table.setVerticalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
28
  self.results_table.setHorizontalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
29
 
30
- # Optimize viewport updates
31
  self.results_table.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
32
  self.results_table.viewport().setProperty("cursor", Qt.CursorShape.ArrowCursor)
33
 
34
- # Set table properties for better performance
35
- self.results_table.setColumnCount(5) # Reduced from 7 to 5 columns
36
  headers = [
37
  "Feature Type", "Chromosome/Scaffold #", "Feature ID/Locus Tag",
38
  "Feature Name", "Feature Description"
39
  ]
40
  self.results_table.setHorizontalHeaderLabels(headers)
41
 
42
- # Set optimized column widths
43
- column_widths = [100, 150, 150, 150, 300] # Adjusted widths
44
  for i, width in enumerate(column_widths):
45
  self.results_table.setColumnWidth(i, width)
46
 
47
  self.results_table.horizontalHeader().setStretchLastSection(True)
48
 
49
- # Connect scroll events for virtual scrolling
50
  self.results_table.verticalScrollBar().valueChanged.connect(self._handle_scroll)
51
-
 
52
  self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
53
 
 
 
54
  def _create_table_item(self, text):
55
  """Optimized item creation"""
56
  item = QTableWidgetItem(str(text))
@@ -146,3 +145,35 @@ class FindTargetsView(QtWidgets.QMainWindow):
146
  self._all_results = []
147
  self._loaded_rows = 0
148
  self.results_table.setUpdatesEnabled(True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  self.results_table.setVerticalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
28
  self.results_table.setHorizontalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
29
 
 
30
  self.results_table.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
31
  self.results_table.viewport().setProperty("cursor", Qt.CursorShape.ArrowCursor)
32
 
33
+ self.results_table.setColumnCount(5)
 
34
  headers = [
35
  "Feature Type", "Chromosome/Scaffold #", "Feature ID/Locus Tag",
36
  "Feature Name", "Feature Description"
37
  ]
38
  self.results_table.setHorizontalHeaderLabels(headers)
39
 
40
+ column_widths = [100, 150, 150, 150, 300]
 
41
  for i, width in enumerate(column_widths):
42
  self.results_table.setColumnWidth(i, width)
43
 
44
  self.results_table.horizontalHeader().setStretchLastSection(True)
45
 
 
46
  self.results_table.verticalScrollBar().valueChanged.connect(self._handle_scroll)
47
+
48
+ self.push_button_generate_library = self.findChild(QPushButton, 'pbtnGenerateLibrary')
49
  self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
50
 
51
+ self.push_button_generate_library.clicked.connect(self._on_generate_library_clicked)
52
+
53
  def _create_table_item(self, text):
54
  """Optimized item creation"""
55
  item = QTableWidgetItem(str(text))
 
145
  self._all_results = []
146
  self._loaded_rows = 0
147
  self.results_table.setUpdatesEnabled(True)
148
+
149
+ def _on_generate_library_clicked(self):
150
+ """Handle generate library button click"""
151
+ try:
152
+ selected_targets = self.get_selected_targets()
153
+ self.global_settings.logger.debug(f"Selected {len(selected_targets)} targets for library generation")
154
+
155
+ if not selected_targets:
156
+ QtWidgets.QMessageBox.warning(
157
+ self,
158
+ "No Selection",
159
+ "Please select targets to generate library."
160
+ )
161
+ return
162
+
163
+ # Create and show generate library window
164
+ self.global_settings.logger.debug("Creating GenerateLibraryController")
165
+ from controllers.GenerateLibraryController import GenerateLibraryController
166
+ generate_library_controller = GenerateLibraryController(
167
+ self.global_settings,
168
+ selected_targets
169
+ )
170
+ self.global_settings.logger.debug("Showing generate library window")
171
+ generate_library_controller.show()
172
+
173
+ except Exception as e:
174
+ self.global_settings.logger.error(f"Error in generate library: {str(e)}")
175
+ QtWidgets.QMessageBox.critical(
176
+ self,
177
+ "Error",
178
+ f"An error occurred while opening the generate library window: {str(e)}"
179
+ )
src/views/GenBankParse.py DELETED
@@ -1,82 +0,0 @@
1
- __author__ = 'brianmendoza'
2
-
3
- from Bio import Entrez, SeqIO
4
- import webbrowser
5
- import re
6
- import os
7
-
8
-
9
- class GenBankFile:
10
-
11
- def __init__(self, organism):
12
- Entrez.email = "bmendoz1@vols.utk.edu"
13
- self.directory = "/Users/brianmendoza/Desktop/GenBank_files/"
14
- self.org = organism
15
-
16
- def setOrg(self, org):
17
- self.org = org
18
-
19
- def setDirectory(self, path, org):
20
- self.directory = path
21
- self.setOrg(org)
22
-
23
- def convertToFasta(self):
24
- orgfile = self.directory + self.org + ".gbff"
25
- output = "/Users/brianmendoza/Desktop/GenBank_files/FASTAs/" + self.org + ".fna"
26
- SeqIO.convert(orgfile, "genbank", output, "fasta")
27
-
28
- def parseAnnotation(self):
29
- gb_file = self.directory + self.org + ".gbff"
30
- records = SeqIO.parse(open(gb_file,"r"), "genbank")
31
-
32
- # create table for multi-targeting reference
33
- table = {}
34
- count = 0
35
- for record in records:
36
- count += 1
37
- chrmnumber = str(count)
38
- table[chrmnumber] = []
39
- for feature in record.features:
40
- if feature.type == 'CDS': # hopefully gene and CDS are the same
41
-
42
- # getting the location...
43
- loc = str(feature.location)
44
- out = re.findall(r"[\d]+", loc)
45
- start = out[0]
46
- end = out[1]
47
- if len(out) > 2: # to account for "joined" domains
48
- end = out[3]
49
-
50
- # locus_tag and product...
51
- if 'locus_tag' in feature.qualifiers:
52
- ltag = feature.qualifiers['locus_tag']
53
- elif 'gene' in feature.qualifiers:
54
- ltag = feature.qualifiers['gene']
55
- if 'product' not in feature.qualifiers:
56
- prod = feature.qualifiers['note']
57
- else:
58
- prod = feature.qualifiers['product']
59
- # adding it all up...
60
- tup = (start, end, ltag, prod)
61
- table[chrmnumber].append(tup)
62
- return table
63
-
64
- def getChromSequence(self, index):
65
- gb_file = self.directory + self.org + ".gbff"
66
- records = SeqIO.parse(open(gb_file,"r"), "genbank")
67
- count = 0
68
- for record in records:
69
- count += 1
70
- if count == index:
71
- cstr = record.seq
72
- return cstr
73
-
74
-
75
- class GffFile:
76
-
77
- def __init__(self, organism):
78
- self.directory = "/Users/brianmendoza/Desktop/GenBank_Files/"
79
- self.org = organism
80
-
81
-
82
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/GenerateLibraryView.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PyQt6 import QtWidgets, uic
2
+ from PyQt6.QtWidgets import QMainWindow, QFileDialog, QWidget
3
+ from PyQt6.QtCore import pyqtSignal, Qt
4
+ import os
5
+ import platform
6
+
7
+ class GenerateLibraryView(QMainWindow):
8
+ # Define signals
9
+ submit_clicked = pyqtSignal(dict) # Signal to emit settings dict when submit is clicked
10
+ progress_updated = pyqtSignal(int)
11
+
12
+ def __init__(self, global_settings):
13
+ super().__init__()
14
+ self.global_settings = global_settings
15
+ self.logger = global_settings.logger
16
+ self._init_ui()
17
+ self._connect_signals()
18
+
19
+ # Set window properties
20
+ self.setWindowModality(Qt.WindowModality.ApplicationModal) # Make window modal
21
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True) # Clean up on close
22
+
23
+ def _init_ui(self):
24
+ try:
25
+ # Load UI file
26
+ ui_file = os.path.join(self.global_settings.get_ui_dir_path(), 'generate_library.ui')
27
+ self.logger.debug(f"Loading UI file from: {ui_file}")
28
+ uic.loadUi(ui_file, self)
29
+
30
+ # Set window properties
31
+ self.setWindowTitle("Generate Library")
32
+ self.setMinimumSize(800, 600) # Set minimum size
33
+
34
+ # Initialize comboboxes
35
+ self._init_guides_per_gene_combo()
36
+ self._init_min_score_combo()
37
+
38
+ # Set default values
39
+ self.ledTargetRangeStart.setText('0')
40
+ self.ledTargetRangeEnd.setText('100')
41
+ self.ledSpaceBetweenGuides.setText('15')
42
+
43
+ # Set default file path
44
+ default_path = self.global_settings.get_db_path()
45
+ if platform.system() == "Windows":
46
+ self.ledFilePath.setText(default_path + "\\")
47
+ else:
48
+ self.ledFilePath.setText(default_path + "/")
49
+
50
+ # Center the window
51
+ self._center_window()
52
+
53
+ except Exception as e:
54
+ self.logger.error(f"Error initializing GenerateLibraryView UI: {str(e)}")
55
+ raise
56
+
57
+ def _center_window(self):
58
+ """Center the window on the screen"""
59
+ try:
60
+ screen = QtWidgets.QApplication.primaryScreen().geometry()
61
+ size = self.geometry()
62
+ x = (screen.width() - size.width()) // 2
63
+ y = (screen.height() - size.height()) // 2
64
+ self.move(x, y)
65
+ except Exception as e:
66
+ self.logger.error(f"Error centering window: {str(e)}")
67
+
68
+ def _init_guides_per_gene_combo(self):
69
+ """Initialize guides per gene combobox"""
70
+ try:
71
+ self.cmbGuidesPerGene.clear()
72
+ for i in range(1, 11):
73
+ self.cmbGuidesPerGene.addItem(str(i))
74
+ except Exception as e:
75
+ self.logger.error(f"Error initializing guides per gene combo: {str(e)}")
76
+
77
+ def _init_min_score_combo(self):
78
+ """Initialize minimum on-target score combobox"""
79
+ try:
80
+ self.cmbMinimumOnTargetScore.clear()
81
+ for i in range(20, 71):
82
+ self.cmbMinimumOnTargetScore.addItem(str(i))
83
+ except Exception as e:
84
+ self.logger.error(f"Error initializing min score combo: {str(e)}")
85
+
86
+ def _connect_signals(self):
87
+ """Connect button signals"""
88
+ try:
89
+ self.pbtnBrowse.clicked.connect(self._browse_output_path)
90
+ self.pbtnCancel.clicked.connect(self.close)
91
+ self.pbtnSubmit.clicked.connect(self._on_submit)
92
+ except Exception as e:
93
+ self.logger.error(f"Error connecting signals: {str(e)}")
94
+
95
+ def showEvent(self, event):
96
+ """Override showEvent to ensure proper window display"""
97
+ super().showEvent(event)
98
+ self.raise_() # Bring window to front
99
+ self.activateWindow() # Activate the window
100
+
101
+ def closeEvent(self, event):
102
+ """Override closeEvent to ensure proper cleanup"""
103
+ try:
104
+ self.logger.debug("Closing GenerateLibraryView")
105
+ super().closeEvent(event)
106
+ except Exception as e:
107
+ self.logger.error(f"Error in closeEvent: {str(e)}")
108
+
109
+ def _browse_output_path(self):
110
+ """Handle browse button click"""
111
+ folder = QFileDialog.getExistingDirectory(
112
+ self,
113
+ "Select Output Directory",
114
+ self.global_settings.get_db_path(),
115
+ QFileDialog.Option.ShowDirsOnly
116
+ )
117
+
118
+ if folder:
119
+ if platform.system() == "Windows":
120
+ self.ledFilePath.setText(folder + "\\")
121
+ else:
122
+ self.ledFilePath.setText(folder + "/")
123
+
124
+ def get_library_settings(self):
125
+ """Get all settings from the UI"""
126
+ try:
127
+ settings = {
128
+ 'guides_per_gene': int(self.cmbGuidesPerGene.currentText()),
129
+ 'target_range_start': float(self.ledTargetRangeStart.text()),
130
+ 'target_range_end': float(self.ledTargetRangeEnd.text()),
131
+ 'space_between_guides': int(self.ledSpaceBetweenGuides.text() or '15'),
132
+ 'min_score': int(self.cmbMinimumOnTargetScore.currentText()),
133
+ 'find_off_targets': self.chkFindOffTargets.isChecked(),
134
+ 'modify_params': self.chkModifyParameters.isChecked(),
135
+ 'five_prime_seq': self.led5PrimeSpecificity.text(),
136
+ 'output_file': os.path.join(
137
+ self.ledFilePath.text(),
138
+ self.ledFileName.text()
139
+ )
140
+ }
141
+
142
+ if self.chkFindOffTargets.isChecked():
143
+ try:
144
+ settings['max_off_target_score'] = float(self.cmbMaximumOffTargetScore.text())
145
+ except ValueError:
146
+ raise ValueError("Invalid maximum off-target score")
147
+
148
+ return settings
149
+
150
+ except ValueError as e:
151
+ raise ValueError(f"Invalid input: {str(e)}")
152
+ except Exception as e:
153
+ raise ValueError(f"Error getting settings: {str(e)}")
154
+
155
+ def update_progress(self, value):
156
+ """Update progress bar"""
157
+ self.progBar.setValue(value)
158
+
159
+ def _on_submit(self):
160
+ """Validate and emit submit signal"""
161
+ try:
162
+ settings = self.get_library_settings()
163
+ # Emit the settings through the signal
164
+ self.submit_clicked.emit(settings)
165
+ except ValueError as e:
166
+ QtWidgets.QMessageBox.critical(
167
+ self,
168
+ "Invalid Input",
169
+ str(e)
170
+ )
171
+
172
+ def show_error(self, title, message):
173
+ """Show error message"""
174
+ QtWidgets.QMessageBox.critical(self, title, message)
175
+
176
+ def show_success(self, message):
177
+ """Show success message"""
178
+ QtWidgets.QMessageBox.information(
179
+ self,
180
+ "Success",
181
+ message
182
+ )
src/views/HomeWindowView.py CHANGED
@@ -12,7 +12,7 @@ class HomeWindowView(QWidget):
12
 
13
  def _init_ui(self) -> None:
14
  try:
15
- uic.loadUi(os.path.join(self.global_settings.get_ui_dir_path(), "home_window_copy.ui"), self)
16
  self._init_ui_elements()
17
  except Exception as e:
18
  self._handle_init_error(e)
@@ -38,6 +38,10 @@ class HomeWindowView(QWidget):
38
  self._init_grpStep2()
39
  self._init_grpStep3()
40
 
 
 
 
 
41
  def _init_grpNavigationMenu(self) -> None:
42
  self.push_button_new_genome = self._find_widget("pbtnNewGenome", QPushButton)
43
  self.push_button_new_endonuclease = self._find_widget("pbtnNewEndonuclease", QPushButton)
@@ -58,10 +62,7 @@ class HomeWindowView(QWidget):
58
  self.radio_button_position = self._find_widget("rbtnPosition", QRadioButton)
59
  self.radio_button_sequence = self._find_widget("rbtnSequence", QRadioButton)
60
  self.text_edit_gene_entry = self._find_widget("txtedGeneEntry", QPlainTextEdit)
61
- self.push_button_find_targets = self._find_widget("pbtnFindTargets", QPushButton)
62
- self.progress_bar_find_targets = self._find_widget("progBarFindTargets", QProgressBar)
63
- self.push_button_view_targets = self._find_widget("pbtnViewTargets", QPushButton)
64
- self.push_button_generate_library = self._find_widget("pbtnGenerateLibrary", QPushButton)
65
 
66
  placeholder_text = ("Example Inputs: \n\n"
67
  "Option 1: Feature (ID, Locus Tag, or Name)\n"
@@ -101,12 +102,6 @@ class HomeWindowView(QWidget):
101
  # self.combo_box_local_annotation_files.clear()
102
  # self.combo_box_local_annotation_files.addItems(annotation_files)
103
 
104
- def set_progress_bar(self, value: int) -> None:
105
- self.progress_bar_find_targets.setValue(value)
106
-
107
- def reset_progress_bar(self) -> None:
108
- self.set_progress_bar(0)
109
-
110
  def get_find_targets_input(self) -> dict:
111
  return {
112
  "organism": self.combo_box_organism.currentText(),
@@ -147,4 +142,83 @@ class HomeWindowView(QWidget):
147
  self.logger.debug("No local annotation files found")
148
 
149
  except Exception as e:
150
- self.logger.error(f"Error updating local annotation files: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  def _init_ui(self) -> None:
14
  try:
15
+ uic.loadUi(os.path.join(self.global_settings.get_ui_dir_path(), "home_window.ui"), self)
16
  self._init_ui_elements()
17
  except Exception as e:
18
  self._handle_init_error(e)
 
38
  self._init_grpStep2()
39
  self._init_grpStep3()
40
 
41
+ # Connect to database manager signals
42
+ self.global_settings.db_manager.db_files_changed.connect(self._handle_db_files_changed)
43
+ self.global_settings.db_manager.db_state_changed.connect(self._handle_db_state_changed)
44
+
45
  def _init_grpNavigationMenu(self) -> None:
46
  self.push_button_new_genome = self._find_widget("pbtnNewGenome", QPushButton)
47
  self.push_button_new_endonuclease = self._find_widget("pbtnNewEndonuclease", QPushButton)
 
62
  self.radio_button_position = self._find_widget("rbtnPosition", QRadioButton)
63
  self.radio_button_sequence = self._find_widget("rbtnSequence", QRadioButton)
64
  self.text_edit_gene_entry = self._find_widget("txtedGeneEntry", QPlainTextEdit)
65
+ self.push_button_find_view_targets = self._find_widget("pbtnFindViewTargets", QPushButton)
 
 
 
66
 
67
  placeholder_text = ("Example Inputs: \n\n"
68
  "Option 1: Feature (ID, Locus Tag, or Name)\n"
 
102
  # self.combo_box_local_annotation_files.clear()
103
  # self.combo_box_local_annotation_files.addItems(annotation_files)
104
 
 
 
 
 
 
 
105
  def get_find_targets_input(self) -> dict:
106
  return {
107
  "organism": self.combo_box_organism.currentText(),
 
142
  self.logger.debug("No local annotation files found")
143
 
144
  except Exception as e:
145
+ self.logger.error(f"Error updating local annotation files: {str(e)}")
146
+
147
+ def show_warning(self, title: str, message: str) -> None:
148
+ """Show a warning message dialog"""
149
+ QtWidgets.QMessageBox.warning(self, title, message)
150
+
151
+ def _update_cspr_related_ui(self) -> None:
152
+ """Update UI elements that depend on CSPR files"""
153
+ try:
154
+ # Store current selections
155
+ current_organism = self.combo_box_organism.currentText()
156
+ current_endo = self.combo_box_endonuclease.currentText()
157
+
158
+ # Get fresh data from controller
159
+ controller = self.global_settings.main_window.controller
160
+ organism_to_endonuclease = controller.get_organism_to_endonuclease()
161
+
162
+ # Update organism combo box
163
+ self.combo_box_organism.clear()
164
+ self.combo_box_organism.addItems(sorted(organism_to_endonuclease.keys()))
165
+
166
+ # Restore organism selection if still valid
167
+ if current_organism in organism_to_endonuclease:
168
+ self.combo_box_organism.setCurrentText(current_organism)
169
+ # Restore endonuclease selection if still valid for this organism
170
+ if current_endo in organism_to_endonuclease[current_organism]:
171
+ self.combo_box_endonuclease.setCurrentText(current_endo)
172
+
173
+ except Exception as e:
174
+ self.logger.error(f"Error updating CSPR-related UI: {str(e)}")
175
+
176
+ def _update_gbff_related_ui(self) -> None:
177
+ """Update UI elements that depend on GBFF files"""
178
+ try:
179
+ # Store current selection
180
+ current_file = self.combo_box_local_annotation_files.currentText()
181
+
182
+ # Update annotation files
183
+ annotation_files = self.global_settings.main_window.controller.get_annotation_files()
184
+ self.update_combo_box_annotation_files(annotation_files)
185
+
186
+ # Restore selection if still valid
187
+ if current_file in annotation_files:
188
+ self.combo_box_local_annotation_files.setCurrentText(current_file)
189
+
190
+ except Exception as e:
191
+ self.logger.error(f"Error updating GBFF-related UI: {str(e)}")
192
+
193
+ def _handle_db_files_changed(self, changes):
194
+ """Handle database file changes"""
195
+ try:
196
+ if (FileChangeType.CSPR_ADDED in changes or
197
+ FileChangeType.CSPR_REMOVED in changes):
198
+ self._update_cspr_related_ui()
199
+
200
+ if (FileChangeType.GBFF_ADDED in changes or
201
+ FileChangeType.GBFF_REMOVED in changes):
202
+ self._update_gbff_related_ui()
203
+
204
+ except Exception as e:
205
+ self.logger.error(f"Error handling database file changes: {str(e)}")
206
+
207
+ def _handle_db_state_changed(self, is_valid, message, changes):
208
+ """Handle database state changes"""
209
+ try:
210
+ if not is_valid:
211
+ self.show_warning("Database Warning", message)
212
+ return
213
+
214
+ if changes: # If there are any changes
215
+ if any(change in changes for change in
216
+ [FileChangeType.CSPR_ADDED, FileChangeType.CSPR_REMOVED]):
217
+ self._update_cspr_related_ui()
218
+
219
+ if any(change in changes for change in
220
+ [FileChangeType.GBFF_ADDED, FileChangeType.GBFF_REMOVED]):
221
+ self._update_gbff_related_ui()
222
+
223
+ except Exception as e:
224
+ self.logger.error(f"Error handling database state change: {str(e)}")
src/views/MainWindowUI.py DELETED
@@ -1,152 +0,0 @@
1
- from PyQt6 import QtWidgets, QtGui, QtCore, uic
2
- import os
3
-
4
- class MainWindowUI(QtWidgets.QMainWindow):
5
- def __init__(self, settings):
6
- super(MainWindowUI, self).__init__()
7
- self.settings = settings
8
- self.setup_ui()
9
-
10
- def setup_ui(self):
11
- # Load the UI file
12
- uic.loadUi(os.path.join(self.settings.get_ui_dir(), 'main_window.ui'), self)
13
-
14
- # Set window properties
15
- self.setWindowTitle("CASPER")
16
- self.setWindowIcon(QtGui.QIcon(os.path.join(self.settings.get_assets_dir(), "cas9image.ico")))
17
-
18
- # Initialize UI components
19
- self.init_ui_components()
20
-
21
- # Set up styles
22
- self.set_styles()
23
-
24
- # Initialize progress bar to 0
25
- self.progressBar.setValue(0)
26
-
27
- # Add the theme toggle button
28
- self.theme_toggle_button = QtWidgets.QPushButton(self)
29
- self.theme_toggle_button.setFixedSize(32, 32)
30
- self.theme_toggle_button.setStyleSheet("border: none;")
31
- self.update_theme_icon()
32
-
33
- # Position the button in the top right corner
34
- self.theme_toggle_button.setGeometry(self.width() - 40, 10, 32, 32)
35
-
36
- # Connect the button to a slot (to be implemented in the controller)
37
- self.theme_toggle_button.clicked.connect(self.on_theme_toggle)
38
-
39
- def init_ui_components(self):
40
- # Initialize and find all the UI components
41
- self.org_choice = self.findChild(QtWidgets.QComboBox, 'orgChoice')
42
- self.endo_choice = self.findChild(QtWidgets.QComboBox, 'endoChoice')
43
- self.annotation_files = self.findChild(QtWidgets.QComboBox, 'annotation_files')
44
- self.gene_entry_field = self.findChild(QtWidgets.QPlainTextEdit, 'gene_entry_field')
45
-
46
- self.push_button_find_targets = self.findChild(QtWidgets.QPushButton, 'pushButton_FindTargets')
47
- self.push_button_view_targets = self.findChild(QtWidgets.QPushButton, 'pushButton_ViewTargets')
48
- self.generate_library = self.findChild(QtWidgets.QPushButton, 'GenerateLibrary')
49
-
50
- self.radio_button_gene = self.findChild(QtWidgets.QRadioButton, 'radioButton_Gene')
51
- self.radio_button_position = self.findChild(QtWidgets.QRadioButton, 'radioButton_Position')
52
- self.radio_button_sequence = self.findChild(QtWidgets.QRadioButton, 'radioButton_Sequence')
53
-
54
- self.new_genome_button = self.findChild(QtWidgets.QPushButton, 'newGenome_button')
55
- self.new_endo_button = self.findChild(QtWidgets.QPushButton, 'newEndo_button')
56
- self.multitargeting_button = self.findChild(QtWidgets.QPushButton, 'multitargeting_button')
57
- self.population_analysis_button = self.findChild(QtWidgets.QPushButton, 'populationAnalysis_button')
58
- self.combine_files_button = self.findChild(QtWidgets.QPushButton, 'combineFiles_button')
59
-
60
- self.progress_bar = self.findChild(QtWidgets.QProgressBar, 'progressBar')
61
-
62
- self.step1 = self.findChild(QtWidgets.QGroupBox, 'Step1')
63
- self.step2 = self.findChild(QtWidgets.QGroupBox, 'Step2')
64
- self.step3 = self.findChild(QtWidgets.QGroupBox, 'Step3')
65
- self.casper_navigation = self.findChild(QtWidgets.QGroupBox, 'CASPER_Navigation')
66
-
67
- self.ncbi_button = self.findChild(QtWidgets.QPushButton, 'ncbi_button')
68
-
69
- # Connect the actionChange_Directory to a slot
70
- self.actionChange_Directory.triggered.connect(self.on_change_directory)
71
-
72
- def set_styles(self):
73
- groupbox_style = """
74
- QGroupBox:title{subcontrol-origin: margin;
75
- left: 10px;
76
- padding: 0 5px 0 5px;}
77
- QGroupBox#Step1{border: 2px solid rgb(111,181,110);
78
- border-radius: 9px;
79
- margin-top: 10px;
80
- font: bold 14pt 'Arial';}
81
- """
82
- self.step1.setStyleSheet(groupbox_style)
83
- self.step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
84
- self.step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
85
- self.casper_navigation.setStyleSheet(groupbox_style.replace("Step1", "CASPER_Navigation")
86
- .replace("solid","dashed")
87
- .replace("rgb(111,181,110)","rgb(88,89,91)"))
88
-
89
- def set_gene_entry_placeholder(self):
90
- placeholder_text = ("Example Inputs: \n\n"
91
- "Option 1: Feature (ID, Locus Tag, or Name)\n"
92
- "Example: 854068/YOL086C/ADH1 for S. cerevisiae alcohol dehydrogenase 1\n\n"
93
- "Option 2: Position (chromosome,start,stop)\n"
94
- "Example: 1,1,1000 for targeting chromosome 1, base pairs 1 to 1000\n\n"
95
- "Option 3: Sequence (must be within the selected organism)\n"
96
- "Example: Any nucleotide sequence between 100 and 10,000 base pairs.\n\n"
97
- "*Note: to multiplex, separate multiple queries by new lines*\n"
98
- "Example:\n"
99
- "1,1,1000\n"
100
- "5,1,500\n"
101
- "etc.")
102
- self.gene_entry_field.setPlaceholderText(placeholder_text)
103
-
104
- def enable_view_targets(self, enable):
105
- self.push_button_view_targets.setEnabled(enable)
106
-
107
- def enable_generate_library(self, enable):
108
- self.generate_library.setEnabled(enable)
109
-
110
- def set_progress(self, value):
111
- if self.progressBar:
112
- self.progressBar.setValue(value)
113
-
114
- def reset_progress(self):
115
- if self.progressBar:
116
- self.progressBar.setValue(0)
117
-
118
- def toggle_annotation(self, gene_checked):
119
- self.step2.setEnabled(True)
120
-
121
- def update_endo_choice(self, endos):
122
- self.endo_choice.clear()
123
- self.endo_choice.addItems(endos)
124
-
125
- def bring_to_front(self):
126
- self.show()
127
- self.setWindowState(self.windowState() & ~QtCore.Qt.WindowState.WindowMinimized | QtCore.Qt.WindowState.WindowActive)
128
- self.raise_()
129
- self.activateWindow()
130
- QtWidgets.QApplication.setActiveWindow(self)
131
-
132
- def on_change_directory(self):
133
- # This method will be connected to the controller
134
- pass
135
-
136
- def update_theme_icon(self):
137
- # Swap the icons: use dark_mode.png for light mode, and light_mode.png for dark mode
138
- icon = QtGui.QIcon(os.path.join(self.settings.get_assets_dir(), "dark_mode.png" if self.settings.is_dark_mode() else "light_mode.png"))
139
- self.theme_toggle_button.setIcon(icon)
140
- self.theme_toggle_button.setIconSize(QtCore.QSize(24, 24))
141
-
142
- # Update the entire application's theme
143
- self.settings.apply_theme()
144
-
145
- def resizeEvent(self, event):
146
- super().resizeEvent(event)
147
- # Reposition the theme toggle button when the window is resized
148
- self.theme_toggle_button.setGeometry(self.width() - 40, 10, 32, 32)
149
-
150
- def on_theme_toggle(self):
151
- # This method will be connected to the controller
152
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/MainWindowView copy.py DELETED
@@ -1,265 +0,0 @@
1
- from PyQt6.QtWidgets import QMainWindow, QPushButton, QRadioButton, QComboBox, QPlainTextEdit, QProgressBar, QMenuBar, QMenu, QStackedWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame
2
- from PyQt6.QtGui import QIcon, QAction, QFont
3
- from PyQt6.QtCore import Qt, QPoint
4
- from PyQt6 import uic, QtWidgets, QtCore
5
- from utils.ui import scale_ui, show_error
6
- import os
7
- from typing import Optional
8
-
9
- class MainWindowView(QMainWindow):
10
- def __init__(self, global_settings):
11
- super().__init__()
12
- self.global_settings = global_settings
13
- self._init_ui()
14
-
15
- def _init_ui(self) -> None:
16
- try:
17
- # self._load_ui_file()
18
- self._init_window_properties()
19
- self._init_custom_title_bar()
20
- self._init_ui_elements()
21
- self._scale_ui()
22
- except Exception as e:
23
- self._handle_init_error(e)
24
-
25
- def _load_ui_file(self) -> None:
26
- ui_file = os.path.join(self.global_settings.get_ui_dir(), "home_window.ui")
27
- uic.loadUi(ui_file, self)
28
-
29
- def _init_window_properties(self) -> None:
30
- self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
31
- self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
32
-
33
- def _init_ui_elements(self) -> None:
34
- # Create a main widget to hold everything
35
- main_widget = QWidget()
36
- main_layout = QVBoxLayout(main_widget)
37
- main_layout.setContentsMargins(0, 0, 0, 0)
38
- main_layout.setSpacing(0)
39
-
40
- # Add the custom title bar
41
- main_layout.addWidget(self.title_bar)
42
-
43
- # Create and add the divider
44
- divider = QFrame()
45
- divider.setFrameShape(QFrame.Shape.HLine)
46
- divider.setFrameShadow(QFrame.Shadow.Sunken)
47
- divider.setStyleSheet("background-color: #c0c0c0;") # Light gray color
48
- divider.setFixedHeight(1) # 1 pixel height
49
- main_layout.addWidget(divider)
50
-
51
- # Create a widget to hold the original content
52
- content_widget = QWidget()
53
- content_layout = QVBoxLayout(content_widget)
54
-
55
- # Move the existing widgets to the content layout
56
- for child in self.children():
57
- if isinstance(child, (QMenuBar, QWidget)) and child != self.title_bar:
58
- content_layout.addWidget(child)
59
-
60
- # Create the stacked widget
61
- self.stacked_widget = QStackedWidget()
62
-
63
- # Add the content widget and stacked widget to the main layout
64
- main_layout.addWidget(content_widget)
65
- main_layout.addWidget(self.stacked_widget)
66
-
67
- # Set the main widget as the central widget
68
- self.setCentralWidget(main_widget)
69
-
70
- # Initialize other UI elements
71
- self._init_menuBar()
72
- self._init_grpNavigationMenu()
73
- self._init_grpStep1()
74
- self._init_grpStep2()
75
- self._init_grpStep3()
76
-
77
- def _init_custom_title_bar(self) -> None:
78
- self.title_bar = QWidget(self)
79
- self.title_bar.setObjectName("custom_title_bar")
80
- self.title_bar.setFixedHeight(32) # Reduced height
81
-
82
- # Create the main horizontal layout for the title bar
83
- layout = QHBoxLayout(self.title_bar)
84
- layout.setContentsMargins(10, 0, 10, 0) # Equal margins on left and right
85
- layout.setSpacing(5) # Reduced spacing between items
86
-
87
- # ----- Window Control Buttons -----
88
- button_font = QFont("Arial", 8) # Smaller font size for button text
89
-
90
- self.minimize_button = QPushButton("-", self.title_bar)
91
- self.minimize_button.setObjectName("minimize_button")
92
- self.minimize_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
93
- self.minimize_button.setFont(button_font)
94
-
95
- self.maximize_button = QPushButton("â›¶", self.title_bar)
96
- self.maximize_button.setObjectName("maximize_button")
97
- self.maximize_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
98
- self.maximize_button.setFont(button_font)
99
-
100
- self.close_button = QPushButton("✕", self.title_bar)
101
- self.close_button.setObjectName("close_button")
102
- self.close_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
103
- self.close_button.setFont(button_font)
104
-
105
- # Apply a style to center the text vertically and horizontally
106
- button_style = """
107
- QPushButton {
108
- padding: 0px;
109
- margin: 0px;
110
- line-height: 20px;
111
- text-align: center;
112
- }
113
- """
114
- self.minimize_button.setStyleSheet(button_style)
115
- self.maximize_button.setStyleSheet(button_style)
116
- self.close_button.setStyleSheet(button_style)
117
-
118
- # ----- Left Widget (Minimize, Maximize, Close) -----
119
- left_widget = QWidget()
120
- left_layout = QHBoxLayout(left_widget)
121
- left_layout.setContentsMargins(0, 0, 0, 0)
122
- left_layout.setSpacing(5)
123
- left_layout.addWidget(self.close_button)
124
- left_layout.addWidget(self.minimize_button)
125
- left_layout.addWidget(self.maximize_button)
126
-
127
- # ----- Theme Toggle Button -----
128
- self.theme_toggle_button = QPushButton(self.title_bar)
129
- self.theme_toggle_button.setObjectName("theme_toggle_button")
130
- self.theme_toggle_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
131
- self.theme_toggle_button.setStyleSheet("border: none;")
132
- self.update_theme_icon()
133
-
134
- # ----- Right Widget (Theme Toggle + Stretch) -----
135
- right_widget = QWidget()
136
- right_layout = QHBoxLayout(right_widget)
137
- right_layout.setContentsMargins(0, 0, 0, 0)
138
- right_layout.setSpacing(5)
139
-
140
- # Add a stretch to push the toggle button to the far right within right_widget
141
- right_layout.addStretch()
142
- right_layout.addWidget(self.theme_toggle_button)
143
-
144
- # ----- Synchronize Widths of Left and Right Widgets -----
145
- # Adjust left_widget to calculate its required width
146
- left_widget.adjustSize()
147
- left_width = left_widget.sizeHint().width()
148
-
149
- # Set right_widget's fixed width to match left_widget's width
150
- right_widget.setFixedWidth(left_width)
151
-
152
- # ----- Title Label -----
153
- self.title_label = QLabel("CASPER", self.title_bar)
154
- self.title_label.setObjectName("title_label")
155
- self.title_label.setFont(QFont("Arial", 10, QFont.Weight.Bold)) # Reduced font size
156
- self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # Center the text in the label
157
- self.title_label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
158
-
159
- # ----- Add Widgets to the Main Title Bar Layout -----
160
- layout.addWidget(left_widget) # Left side buttons
161
- layout.addStretch(1) # Stretchable space
162
- layout.addWidget(self.title_label) # Centered title
163
- layout.addStretch(1) # Stretchable space
164
- layout.addWidget(right_widget) # Right side buttons (theme toggle + stretch)
165
-
166
- # Optional: Ensure the title_label is truly centered
167
- self.title_label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
168
- def _init_menuBar(self) -> None:
169
- self.action_change_directory = self._find_widget("actChangeDirectory", QAction)
170
- self.action_open_genome_browser = self._find_widget("actOpenGenomeBrowser", QAction)
171
- self.action_open_repository = self._find_widget("actOpenRepository", QAction)
172
- self.action_open_NCBI_BLAST = self._find_widget("actOpenNCBIBLAST", QAction)
173
- self.action_open_NCBI = self._find_widget("actOpenNCBI", QAction)
174
-
175
- def _init_grpNavigationMenu(self) -> None:
176
- self.push_button_new_genome = self._find_widget("pbtnNewGenome", QPushButton)
177
- self.push_button_new_endonuclease = self._find_widget("pbtnNewEndonuclease", QPushButton)
178
- self.push_button_multitargeting_analysis = self._find_widget("pbtnMultitargetingAnalysis", QPushButton)
179
- self.push_button_population_analysis = self._find_widget("pbtnPopulationAnalysis", QPushButton)
180
- self.push_button_combine_files = self._find_widget("pbtnCombineFiles", QPushButton)
181
-
182
- def _init_grpStep1(self) -> None:
183
- self.combo_box_organism = self._find_widget("cmbOrganism", QComboBox)
184
- self.combo_box_endonuclease = self._find_widget("cmbEndonuclease", QComboBox)
185
-
186
- def _init_grpStep2(self) -> None:
187
- self.push_button_ncbi_file_search = self._find_widget("pbtnNCBIFileSearch", QPushButton)
188
- self.combo_box_local_annotation_files = self._find_widget("cmbLocalAnnotationFiles", QComboBox)
189
-
190
- def _init_grpStep3(self) -> None:
191
- self.radio_button_feature = self._find_widget("rbtnFeature", QRadioButton)
192
- self.radio_button_position = self._find_widget("rbtnPosition", QRadioButton)
193
- self.radio_button_sequence = self._find_widget("rbtnSequence", QRadioButton)
194
- self.text_edit_gene_entry = self._find_widget("txtedGeneEntry", QPlainTextEdit)
195
- self.push_button_find_targets = self._find_widget("pbtnFindTargets", QPushButton)
196
- self.progress_bar_find_targets = self._find_widget("progBarFindTargets", QProgressBar)
197
- self.push_button_view_targets = self._find_widget("pbtnViewTargets", QPushButton)
198
- self.push_button_generate_library = self._find_widget("pbtnGenerateLibrary", QPushButton)
199
-
200
- placeholder_text = ("Example Inputs: \n\n"
201
- "Option 1: Feature (ID, Locus Tag, or Name)\n"
202
- "Example: 854068/YOL086C/ADH1 for S. cerevisiae alcohol dehydrogenase 1\n\n"
203
- "Option 2: Position (chromosome,start,stop)\n"
204
- "Example: 1,1,1000 for targeting chromosome 1, base pairs 1 to 1000\n\n"
205
- "Option 3: Sequence (must be within the selected organism)\n"
206
- "Example: Any nucleotide sequence between 100 and 10,000 base pairs.\n\n"
207
- "*Note: to multiplex, separate multiple queries by new lines*\n"
208
- "Example:\n"
209
- "1,1,1000\n"
210
- "5,1,500\n"
211
- "etc.")
212
- self.text_edit_gene_entry.setPlaceholderText(placeholder_text)
213
-
214
- def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
215
- widget = self.findChild(widget_type, name)
216
- if widget is None:
217
- self.global_settings.logger.warning(f"Widget '{name}' not found in UI file.")
218
- return widget
219
-
220
- def _scale_ui(self) -> None:
221
- scale_ui(self, custom_scale_width=1000, custom_scale_height=350)
222
-
223
- def _handle_init_error(self, e: Exception) -> None:
224
- error_msg = f"Error initializing MainWindowView: {str(e)}"
225
- self.global_settings.logger.error(error_msg, exc_info=True)
226
- show_error(self.global_settings, "Initialization Error", error_msg)
227
- raise
228
-
229
- def update_combo_box_endonuclease(self, endonuclease: list) -> None:
230
- self.combo_box_endonuclease.clear()
231
- self.combo_box_endonuclease.addItems(endonuclease)
232
-
233
- def update_combo_box_organism(self, organisms: list) -> None:
234
- self.combo_box_organism.clear()
235
- self.combo_box_organism.addItems(organisms)
236
-
237
- def update_combo_box_annotation_files(self, annotation_files: list) -> None:
238
- self.combo_box_local_annotation_files.clear()
239
- self.combo_box_local_annotation_files.addItems(annotation_files)
240
-
241
- def set_progress_bar(self, value: int) -> None:
242
- self.progress_bar_find_targets.setValue(value)
243
-
244
- def reset_progress_bar(self) -> None:
245
- self.set_progress_bar(0)
246
-
247
- def update_theme_icon(self) -> None:
248
- icon_name = "dark_mode.png" if self.global_settings.is_dark_mode() else "light_mode.png"
249
- icon_path = os.path.join(self.global_settings.get_assets_dir(), icon_name)
250
- icon = QIcon(icon_path)
251
- self.theme_toggle_button.setIcon(icon)
252
- self.theme_toggle_button.setIconSize(QtCore.QSize(16, 16)) # Reduced icon size from 18x18 to 16x16
253
-
254
- def mousePressEvent(self, event):
255
- if event.button() == Qt.MouseButton.LeftButton and self.title_bar.underMouse():
256
- self.drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
257
- event.accept()
258
-
259
- def mouseMoveEvent(self, event):
260
- if event.buttons() & Qt.MouseButton.LeftButton and self.drag_position:
261
- self.move(event.globalPosition().toPoint() - self.drag_position)
262
- event.accept()
263
-
264
- def mouseReleaseEvent(self, event):
265
- self.drag_position = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/views/MainWindowView.py CHANGED
@@ -1,6 +1,6 @@
1
  from PyQt6.QtWidgets import (
2
  QMainWindow, QPushButton, QWidget, QVBoxLayout,
3
- QHBoxLayout, QLabel, QFrame,
4
  )
5
  from PyQt6.QtGui import QIcon, QAction
6
  from PyQt6.QtCore import Qt
@@ -17,6 +17,7 @@ class MainWindowView(QMainWindow, LoggingMixin):
17
  QMainWindow.__init__(self)
18
  LoggingMixin.__init__(self)
19
  self.settings = global_settings
 
20
  self._init_ui()
21
  self.oldPos = None
22
 
@@ -149,22 +150,76 @@ class MainWindowView(QMainWindow, LoggingMixin):
149
  left_layout.addWidget(self.minimize_window_button)
150
  left_layout.addWidget(self.maximize_window_button)
151
 
152
- # ----- Theme Toggle Button -----
153
- self.theme_toggle_button = QPushButton(self.title_bar)
154
- self.theme_toggle_button.setObjectName("theme_toggle_button")
155
- self.theme_toggle_button.setFixedSize(20, 20)
156
- self.theme_toggle_button.setStyleSheet("border: none;")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  self.update_theme_icon()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
- # ----- Right Widget (Theme Toggle + Stretch) -----
160
  right_widget = QWidget()
161
  right_layout = QHBoxLayout(right_widget)
162
  right_layout.setContentsMargins(0, 0, 0, 0)
163
  right_layout.setSpacing(5)
164
 
165
- # Add a stretch to push the toggle button to the far right within right_widget
166
  right_layout.addStretch()
167
- right_layout.addWidget(self.theme_toggle_button)
 
168
 
169
  # Adjust left_widget to calculate its required width
170
  left_widget.adjustSize()
@@ -205,15 +260,38 @@ class MainWindowView(QMainWindow, LoggingMixin):
205
 
206
  def update_theme_icon(self) -> None:
207
  try:
 
208
  icon_name = "dark_mode.png" if self.settings.get_theme() == "dark" else "light_mode.png"
209
  icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
210
- icon = QIcon(icon_path)
211
- self.theme_toggle_button.setIcon(icon)
212
- self.theme_toggle_button.setIconSize(QtCore.QSize(16, 16))
213
  except Exception as e:
214
  self.log_error("update_theme_icon", e)
215
  show_error(self.settings, "Theme Error", "Failed to update theme icon")
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  def resizeEvent(self, event):
218
  super().resizeEvent(event)
219
  self.log_debug(f"Window resized. New size: {self.size()}")
@@ -234,7 +312,9 @@ class MainWindowView(QMainWindow, LoggingMixin):
234
  "tab_border_color": "#444444",
235
  "tab_selected_border_color": "#51b85e",
236
  "tab_hover_bg_color": "#3b3b3b",
237
- "divider_color": "#444444"
 
 
238
  },
239
  "light": {
240
  "bg_color": "#f0f0f0",
@@ -250,7 +330,9 @@ class MainWindowView(QMainWindow, LoggingMixin):
250
  "tab_border_color": "#c0c0c0",
251
  "tab_selected_border_color": "#51b85e",
252
  "tab_hover_bg_color": "#e0e0e0",
253
- "divider_color": "#c0c0c0"
 
 
254
  }
255
  }
256
 
@@ -258,7 +340,7 @@ class MainWindowView(QMainWindow, LoggingMixin):
258
  theme = themes["dark"] if current_theme == "dark" else themes["light"]
259
  qdarktheme.setup_theme(current_theme)
260
 
261
- # Set the stylesheet
262
  self.setStyleSheet(f"""
263
  QWidget {{ background-color: {theme['bg_color']}; color: {theme['fg_color']}; }}
264
  QPushButton {{ background-color: {theme['button_bg_color']}; border: 1px solid {theme['button_border_color']}; }}
@@ -270,6 +352,31 @@ class MainWindowView(QMainWindow, LoggingMixin):
270
  QMenu {{ background-color: {theme['menu_bg_color']}; }}
271
  QMenu::item:selected {{ background-color: {theme['menu_item_hover_bg_color']}; }}
272
  QFrame#custom_divider {{ border-bottom: 1px solid {theme['divider_color']}; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  """)
274
 
275
  # Set the tab widget stylesheet
@@ -305,6 +412,50 @@ class MainWindowView(QMainWindow, LoggingMixin):
305
  }}
306
  """)
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  def mousePressEvent(self, event):
309
  """Handle mouse press events for window dragging"""
310
  if event.button() == Qt.MouseButton.LeftButton:
 
1
  from PyQt6.QtWidgets import (
2
  QMainWindow, QPushButton, QWidget, QVBoxLayout,
3
+ QHBoxLayout, QLabel, QFrame, QMenu,
4
  )
5
  from PyQt6.QtGui import QIcon, QAction
6
  from PyQt6.QtCore import Qt
 
17
  QMainWindow.__init__(self)
18
  LoggingMixin.__init__(self)
19
  self.settings = global_settings
20
+ self.action_toggle_theme = QAction("Toggle Theme", self)
21
  self._init_ui()
22
  self.oldPos = None
23
 
 
150
  left_layout.addWidget(self.minimize_window_button)
151
  left_layout.addWidget(self.maximize_window_button)
152
 
153
+ # ----- Add Button with Dropdown -----
154
+ self.add_button = QPushButton(self.title_bar)
155
+ self.add_button.setObjectName("add_button")
156
+ self.add_button.setFixedSize(20, 20)
157
+
158
+ # Create the dropdown menu
159
+ self.add_menu = QMenu(self.add_button)
160
+ self.add_menu.setObjectName("add_menu")
161
+
162
+ # Add actions to the menu
163
+ self.action_new_genome = self.add_menu.addAction("New Genome")
164
+ self.action_new_endonuclease = self.add_menu.addAction("New Endonuclease")
165
+
166
+ # Set the menu for the button
167
+ self.add_button.setMenu(self.add_menu)
168
+ self.add_button.setStyleSheet("""
169
+ QPushButton {
170
+ padding: 0px;
171
+ margin: 0px;
172
+ text-align: center;
173
+ border: none;
174
+ }
175
+ QPushButton::menu-indicator {
176
+ width: 0px;
177
+ }
178
+ """)
179
+
180
+ # Initial icon will be set in update_plus_icon method
181
+ self.update_plus_icon()
182
+
183
+ # ----- Settings Button with Dropdown -----
184
+ self.settings_button = QPushButton(self.title_bar)
185
+ self.settings_button.setObjectName("settings_button")
186
+ self.settings_button.setFixedSize(20, 20)
187
+
188
+ # Create the settings dropdown menu
189
+ self.settings_menu = QMenu(self.settings_button)
190
+ self.settings_menu.setObjectName("settings_menu")
191
+
192
+ # Update the theme icon and add the action to the menu
193
  self.update_theme_icon()
194
+ self.settings_menu.addAction(self.action_toggle_theme)
195
+
196
+ # Set the menu for the button
197
+ self.settings_button.setMenu(self.settings_menu)
198
+ self.settings_button.setStyleSheet("""
199
+ QPushButton {
200
+ padding: 0px;
201
+ margin: 0px;
202
+ text-align: center;
203
+ border: none;
204
+ }
205
+ QPushButton::menu-indicator {
206
+ width: 0px;
207
+ }
208
+ """)
209
+
210
+ # Initial icon will be set in update_settings_icon method
211
+ self.update_settings_icon()
212
 
213
+ # ----- Right Widget (Add Button + Settings Button + Stretch) -----
214
  right_widget = QWidget()
215
  right_layout = QHBoxLayout(right_widget)
216
  right_layout.setContentsMargins(0, 0, 0, 0)
217
  right_layout.setSpacing(5)
218
 
219
+ # Add a stretch to push the buttons to the far right within right_widget
220
  right_layout.addStretch()
221
+ right_layout.addWidget(self.add_button)
222
+ right_layout.addWidget(self.settings_button)
223
 
224
  # Adjust left_widget to calculate its required width
225
  left_widget.adjustSize()
 
260
 
261
  def update_theme_icon(self) -> None:
262
  try:
263
+ # Update the theme action icon
264
  icon_name = "dark_mode.png" if self.settings.get_theme() == "dark" else "light_mode.png"
265
  icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
266
+ self.action_toggle_theme.setIcon(QIcon(icon_path))
 
 
267
  except Exception as e:
268
  self.log_error("update_theme_icon", e)
269
  show_error(self.settings, "Theme Error", "Failed to update theme icon")
270
 
271
+ def update_plus_icon(self) -> None:
272
+ """Update the plus icon based on current theme"""
273
+ try:
274
+ icon_name = "plus_white.png" if self.settings.get_theme() == "dark" else "plus_dark.png"
275
+ icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
276
+ icon = QIcon(icon_path)
277
+ self.add_button.setIcon(icon)
278
+ self.add_button.setIconSize(QtCore.QSize(14, 14))
279
+ except Exception as e:
280
+ self.log_error("update_plus_icon", e)
281
+ show_error(self.settings, "Theme Error", "Failed to update plus icon")
282
+
283
+ def update_settings_icon(self) -> None:
284
+ """Update the settings icon based on current theme"""
285
+ try:
286
+ icon_name = "settings_light.png" if self.settings.get_theme() == "dark" else "settings_dark.png"
287
+ icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
288
+ icon = QIcon(icon_path)
289
+ self.settings_button.setIcon(icon)
290
+ self.settings_button.setIconSize(QtCore.QSize(16, 16))
291
+ except Exception as e:
292
+ self.log_error("update_settings_icon", e)
293
+ show_error(self.settings, "Theme Error", "Failed to update settings icon")
294
+
295
  def resizeEvent(self, event):
296
  super().resizeEvent(event)
297
  self.log_debug(f"Window resized. New size: {self.size()}")
 
312
  "tab_border_color": "#444444",
313
  "tab_selected_border_color": "#51b85e",
314
  "tab_hover_bg_color": "#3b3b3b",
315
+ "divider_color": "#444444",
316
+ "menu_text_color": "#ffffff",
317
+ "menu_hover_text_color": "#ffffff",
318
  },
319
  "light": {
320
  "bg_color": "#f0f0f0",
 
330
  "tab_border_color": "#c0c0c0",
331
  "tab_selected_border_color": "#51b85e",
332
  "tab_hover_bg_color": "#e0e0e0",
333
+ "divider_color": "#c0c0c0",
334
+ "menu_text_color": "#000000",
335
+ "menu_hover_text_color": "#000000",
336
  }
337
  }
338
 
 
340
  theme = themes["dark"] if current_theme == "dark" else themes["light"]
341
  qdarktheme.setup_theme(current_theme)
342
 
343
+ # Update the existing stylesheet with menu styling
344
  self.setStyleSheet(f"""
345
  QWidget {{ background-color: {theme['bg_color']}; color: {theme['fg_color']}; }}
346
  QPushButton {{ background-color: {theme['button_bg_color']}; border: 1px solid {theme['button_border_color']}; }}
 
352
  QMenu {{ background-color: {theme['menu_bg_color']}; }}
353
  QMenu::item:selected {{ background-color: {theme['menu_item_hover_bg_color']}; }}
354
  QFrame#custom_divider {{ border-bottom: 1px solid {theme['divider_color']}; }}
355
+
356
+ QPushButton#add_button {{
357
+ background-color: {theme['button_bg_color']};
358
+ color: {theme['fg_color']};
359
+ border: 1px solid {theme['button_border_color']};
360
+ padding: 0px;
361
+ font-size: 16px;
362
+ line-height: 20px;
363
+ }}
364
+
365
+ QPushButton#add_button:hover {{
366
+ background-color: {theme['button_hover_bg_color']};
367
+ }}
368
+
369
+ QMenu {{
370
+ background-color: {theme['menu_bg_color']};
371
+ color: {theme['menu_text_color']};
372
+ border: 1px solid {theme['button_border_color']};
373
+ padding: 5px;
374
+ }}
375
+
376
+ QMenu::item:selected {{
377
+ background-color: {theme['menu_item_hover_bg_color']};
378
+ color: {theme['menu_hover_text_color']};
379
+ }}
380
  """)
381
 
382
  # Set the tab widget stylesheet
 
412
  }}
413
  """)
414
 
415
+ # Update the add button styling in the theme
416
+ self.add_button.setStyleSheet(f"""
417
+ QPushButton {{
418
+ padding: 0px;
419
+ margin: 0px;
420
+ line-height: 0px;
421
+ text-align: center;
422
+ border: none;
423
+ font-size: 14px;
424
+ color: {theme['fg_color']};
425
+ }}
426
+ QPushButton:hover {{
427
+ background-color: {theme['button_hover_bg_color']};
428
+ }}
429
+ QPushButton::menu-indicator {{
430
+ width: 0px;
431
+ }}
432
+ """)
433
+
434
+ # Update the settings button styling
435
+ self.settings_button.setStyleSheet(f"""
436
+ QPushButton {{
437
+ padding: 0px;
438
+ margin: 0px;
439
+ line-height: 0px;
440
+ text-align: center;
441
+ border: none;
442
+ font-size: 14px;
443
+ color: {theme['fg_color']};
444
+ background-color: transparent;
445
+ }}
446
+ QPushButton:hover {{
447
+ background-color: {theme['button_hover_bg_color']};
448
+ }}
449
+ QPushButton::menu-indicator {{
450
+ width: 0px;
451
+ }}
452
+ """)
453
+
454
+ # Update icons
455
+ self.update_theme_icon()
456
+ self.update_plus_icon()
457
+ self.update_settings_icon()
458
+
459
  def mousePressEvent(self, event):
460
  """Handle mouse press events for window dragging"""
461
  if event.button() == Qt.MouseButton.LeftButton:
src/views/MultitargetingWindowView.py CHANGED
@@ -27,20 +27,34 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
27
  self._init_grpSeedAnalysis()
28
  self._init_grpGlobalAnalysis()
29
 
 
 
30
  def _init_grpSelectOrganism(self):
31
  self.combo_box_organism = self._find_widget('cmbOrganism', QtWidgets.QComboBox)
32
  self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
 
33
  self.push_button_analyze = self._find_widget('pbtnAnalyze', QtWidgets.QPushButton)
34
  self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
35
- self.tool_button_sql_settings = self._find_widget('tbtnSQLSettings', QtWidgets.QToolButton)
36
  self.table_seeds = self._find_widget('tblSeeds', QtWidgets.QTableWidget)
37
 
 
38
  self.table_seeds.setColumnCount(8)
39
- self.table_seeds.setHorizontalHeaderLabels(["Seed", "Total Repeats", "Avg. Repeats/Scaffold", "Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"])
 
 
 
 
 
40
  self.table_seeds.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
41
  self.table_seeds.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
42
- self.table_seeds.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
43
- self.table_seeds.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeMode.Stretch)
 
 
 
 
 
 
44
 
45
  def _init_grpSeedAnalysis(self):
46
  # Get the tab widget
@@ -77,11 +91,10 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
77
  self.scroll_chromosome.viewport().installEventFilter(self)
78
  self.graphical_view_chromosome.viewport().installEventFilter(self)
79
 
80
- # Dictionary to map canvases to chromosomes
81
  self.canvas_chromosome_map = {}
82
 
83
  def _init_grpGlobalAnalysis(self):
84
- self.push_button_statistics_overview = self._find_widget('pbtnStatisticsOverview', QtWidgets.QPushButton)
85
 
86
  self.tab_repeats_vs_seed = self._find_widget('tabRepeatsVsSeed', QtWidgets.QWidget)
87
  self.plot_repeats_vs_seed = self._find_widget('plotRepeatsVsSeed', QtWidgets.QWidget)
@@ -96,7 +109,6 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
96
  return widget
97
 
98
  def update_seeds_table(self, data):
99
- """Update the seeds table with the provided data"""
100
  self.table_seeds.setRowCount(len(data))
101
  for row, row_data in enumerate(data):
102
  # Unpack the data
@@ -155,18 +167,11 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
155
  y1 = data['counts']
156
  x = range(len(y1))
157
 
158
- # Plot with larger markers and line width for better visibility
159
  self.repeats_vs_seed_canvas.axes.plot(x, y1, linewidth=1.5, marker='.', markersize=3)
160
-
161
- # Set labels and title with larger font sizes
162
- self.repeats_vs_seed_canvas.axes.set_xlabel('Seed ID Number', fontsize=12)
163
- self.repeats_vs_seed_canvas.axes.set_ylabel('Number of Repeats', fontsize=12)
164
- self.repeats_vs_seed_canvas.axes.set_title('Number of Repeats per Seed ID Number', fontsize=14)
165
-
166
- # Set tick label size
167
- self.repeats_vs_seed_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
168
-
169
- # Add grid for better readability
170
  self.repeats_vs_seed_canvas.axes.grid(True, linestyle='--', alpha=0.7)
171
 
172
  # Store statistics if needed
@@ -192,32 +197,19 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
192
  self.sequences_vs_repeats_canvas.axes.clear()
193
 
194
  if data and 'x_vals' in data and 'y_vals' in data:
195
- x = data['x_vals']
196
- y = data['y_vals']
197
-
198
- # Create scatter plot with specific style
199
- self.sequences_vs_repeats_canvas.axes.scatter(x, y, s=15, color='blue')
200
 
201
- # Set y-axis to log scale
202
  self.sequences_vs_repeats_canvas.axes.set_yscale('log')
203
-
204
- # Set labels and title
205
- self.sequences_vs_repeats_canvas.axes.set_xlabel('Number of Repeats', fontsize=12)
206
- self.sequences_vs_repeats_canvas.axes.set_ylabel('Number of Sequences', fontsize=12)
207
- self.sequences_vs_repeats_canvas.axes.set_title('Number of Sequences per Number of Repeats', fontsize=14)
208
-
209
- # Set tick label size
210
- self.sequences_vs_repeats_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
211
-
212
- # Add grid for better readability
213
  self.sequences_vs_repeats_canvas.axes.grid(True, linestyle='--', alpha=0.7)
214
 
215
- # Force integer ticks on x-axis
216
- self.sequences_vs_repeats_canvas.axes.xaxis.set_major_locator(MaxNLocator(integer=True))
217
-
218
- # Set axis ranges to match the image
219
- self.sequences_vs_repeats_canvas.axes.set_xlim(0, max(x) + 5) # Add some padding
220
- self.sequences_vs_repeats_canvas.axes.set_ylim(1, 10**4) # Log scale from 1 to 10^4
221
 
222
  self.sequences_vs_repeats_canvas.draw()
223
 
@@ -274,7 +266,6 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
274
  self.logger.error(f"Error updating repeat vs chromosome plot: {str(e)}")
275
 
276
  def fill_chromosome_viewer(self, seed_data, event_data):
277
- """Fill the chromosome viewer with visualization"""
278
  try:
279
  # Clear out old widgets in layout
280
  for i in reversed(range(self.chromosome_layout.count())):
@@ -362,6 +353,27 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
362
  except Exception as e:
363
  self.logger.error(f"Error in chromosome event handler: {str(e)}")
364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  class MplCanvas(FigureCanvasQTAgg):
366
  def __init__(self, parent=None, width=8, height=6, dpi=100):
367
  fig = Figure(figsize=(width, height), dpi=dpi, tight_layout=True)
 
27
  self._init_grpSeedAnalysis()
28
  self._init_grpGlobalAnalysis()
29
 
30
+ self.push_button_export_selected_gRNAs = self._find_widget('pbtnExportSelectedgRNAs', QtWidgets.QPushButton)
31
+
32
  def _init_grpSelectOrganism(self):
33
  self.combo_box_organism = self._find_widget('cmbOrganism', QtWidgets.QComboBox)
34
  self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
35
+ self.line_edit_max_results = self._find_widget('ledMaxResults', QtWidgets.QLineEdit)
36
  self.push_button_analyze = self._find_widget('pbtnAnalyze', QtWidgets.QPushButton)
37
  self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
 
38
  self.table_seeds = self._find_widget('tblSeeds', QtWidgets.QTableWidget)
39
 
40
+ # Set up table columns
41
  self.table_seeds.setColumnCount(8)
42
+ self.table_seeds.setHorizontalHeaderLabels([
43
+ "Seed", "Total Repeats", "Avg. Repeats/Scaffold",
44
+ "Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
45
+ ])
46
+
47
+ # Set table properties
48
  self.table_seeds.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
49
  self.table_seeds.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
50
+ self.table_seeds.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
51
+ # Set minimum width for the table
52
+ self.table_seeds.setMinimumWidth(650)
53
+
54
+ # Add validation for max results line edit
55
+ self.line_edit_max_results.setValidator(QtGui.QIntValidator())
56
+ # Set default value
57
+ self.line_edit_max_results.setText("1000")
58
 
59
  def _init_grpSeedAnalysis(self):
60
  # Get the tab widget
 
91
  self.scroll_chromosome.viewport().installEventFilter(self)
92
  self.graphical_view_chromosome.viewport().installEventFilter(self)
93
 
 
94
  self.canvas_chromosome_map = {}
95
 
96
  def _init_grpGlobalAnalysis(self):
97
+ self.tab_statistics_overview = self._find_widget('tabStatisticsOverview', QtWidgets.QWidget)
98
 
99
  self.tab_repeats_vs_seed = self._find_widget('tabRepeatsVsSeed', QtWidgets.QWidget)
100
  self.plot_repeats_vs_seed = self._find_widget('plotRepeatsVsSeed', QtWidgets.QWidget)
 
109
  return widget
110
 
111
  def update_seeds_table(self, data):
 
112
  self.table_seeds.setRowCount(len(data))
113
  for row, row_data in enumerate(data):
114
  # Unpack the data
 
167
  y1 = data['counts']
168
  x = range(len(y1))
169
 
 
170
  self.repeats_vs_seed_canvas.axes.plot(x, y1, linewidth=1.5, marker='.', markersize=3)
171
+ self.repeats_vs_seed_canvas.axes.set_xlabel('Seed ID Number', fontsize=10)
172
+ self.repeats_vs_seed_canvas.axes.set_ylabel('Number of Repeats', fontsize=10)
173
+ self.repeats_vs_seed_canvas.axes.set_title('Number of Repeats per Seed ID Number', fontsize=10)
174
+ self.repeats_vs_seed_canvas.axes.tick_params(axis='both', which='major', labelsize=8)
 
 
 
 
 
 
175
  self.repeats_vs_seed_canvas.axes.grid(True, linestyle='--', alpha=0.7)
176
 
177
  # Store statistics if needed
 
197
  self.sequences_vs_repeats_canvas.axes.clear()
198
 
199
  if data and 'x_vals' in data and 'y_vals' in data:
200
+ x = data['x_vals'] # Number of repeats
201
+ y = data['y_vals'] # Number of sequences
 
 
 
202
 
203
+ self.sequences_vs_repeats_canvas.axes.scatter(x, y, s=10)
204
  self.sequences_vs_repeats_canvas.axes.set_yscale('log')
205
+ self.sequences_vs_repeats_canvas.axes.set_xlabel('Number of Repeats', fontsize=10)
206
+ self.sequences_vs_repeats_canvas.axes.set_ylabel('Number of Sequences', fontsize=10)
207
+ self.sequences_vs_repeats_canvas.axes.set_title('Number of Sequences per Number of Repeats', fontsize=10)
208
+ self.sequences_vs_repeats_canvas.axes.tick_params(axis='both', which='major', labelsize=8)
 
 
 
 
 
 
209
  self.sequences_vs_repeats_canvas.axes.grid(True, linestyle='--', alpha=0.7)
210
 
211
+ if x:
212
+ self.sequences_vs_repeats_canvas.axes.set_xlim(x[0] - 0.5, x[-1] + 0.5)
 
 
 
 
213
 
214
  self.sequences_vs_repeats_canvas.draw()
215
 
 
266
  self.logger.error(f"Error updating repeat vs chromosome plot: {str(e)}")
267
 
268
  def fill_chromosome_viewer(self, seed_data, event_data):
 
269
  try:
270
  # Clear out old widgets in layout
271
  for i in reversed(range(self.chromosome_layout.count())):
 
353
  except Exception as e:
354
  self.logger.error(f"Error in chromosome event handler: {str(e)}")
355
 
356
+ def update_statistics_labels(self, total_repeats, avg_repeats, median_repeats, mode_repeats):
357
+ """Update the statistics overview labels with new values"""
358
+ try:
359
+ # Find and update the statistics labels
360
+ total_label = self._find_widget('lblTotalRepeatsValue', QtWidgets.QLabel)
361
+ avg_label = self._find_widget('lblAverageRepeatsValue', QtWidgets.QLabel)
362
+ median_label = self._find_widget('lblMedianRepeatsValue', QtWidgets.QLabel)
363
+ mode_label = self._find_widget('lblModeRepeatsValue', QtWidgets.QLabel)
364
+
365
+ if total_label:
366
+ total_label.setText(str(round(float(total_repeats), 1)))
367
+ if avg_label:
368
+ avg_label.setText(str(round(float(avg_repeats), 1)))
369
+ if median_label:
370
+ median_label.setText(str(round(float(median_repeats), 1)))
371
+ if mode_label:
372
+ mode_label.setText(str(round(float(mode_repeats), 1)))
373
+
374
+ except Exception as e:
375
+ self.logger.error(f"Error updating statistics labels: {str(e)}")
376
+
377
  class MplCanvas(FigureCanvasQTAgg):
378
  def __init__(self, parent=None, width=8, height=6, dpi=100):
379
  fig = Figure(figsize=(width, height), dpi=dpi, tight_layout=True)
src/views/NCBIWindowView.py CHANGED
@@ -18,7 +18,7 @@ class NCBIWindowView(QtWidgets.QMainWindow):
18
  def _setup_basic_ui(self):
19
  """Initial minimal setup to show the window quickly"""
20
  try:
21
- uic.loadUi(os.path.join(self.settings.get_ui_dir_path(), "ncbi_window_v2.ui"), self)
22
 
23
  QtCore.QTimer.singleShot(100, self._complete_initialization)
24
 
 
18
  def _setup_basic_ui(self):
19
  """Initial minimal setup to show the window quickly"""
20
  try:
21
+ uic.loadUi(os.path.join(self.settings.get_ui_dir_path(), "ncbi.ui"), self)
22
 
23
  QtCore.QTimer.singleShot(100, self._complete_initialization)
24
 
src/views/NewEndonucleaseView.py CHANGED
@@ -2,14 +2,139 @@ from typing import Optional
2
  from PyQt6 import QtWidgets, QtGui, QtCore, uic
3
  from utils.ui import show_error
4
  import os
 
5
 
6
  class NewEndonucleaseView(QtWidgets.QMainWindow):
7
  def __init__(self, settings):
8
  super().__init__()
9
  self.settings = settings
10
  self.logger = self.settings.get_logger()
11
-
 
 
 
 
 
 
 
 
 
12
  self.init_ui()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def init_ui(self):
15
  try:
 
2
  from PyQt6 import QtWidgets, QtGui, QtCore, uic
3
  from utils.ui import show_error
4
  import os
5
+ import qdarktheme
6
 
7
  class NewEndonucleaseView(QtWidgets.QMainWindow):
8
  def __init__(self, settings):
9
  super().__init__()
10
  self.settings = settings
11
  self.logger = self.settings.get_logger()
12
+
13
+ # Set window properties
14
+ self.setWindowTitle("New Endonuclease")
15
+ self.setMinimumSize(400, 500) # Set minimum window size
16
+
17
+ # Center the window on screen
18
+ screen = QtGui.QGuiApplication.primaryScreen()
19
+ screen_geometry = screen.geometry()
20
+ centerPoint = screen_geometry.center()
21
+
22
  self.init_ui()
23
+
24
+ # Calculate and set position to center
25
+ frame_geometry = self.frameGeometry()
26
+ frame_geometry.moveCenter(centerPoint)
27
+ self.move(frame_geometry.topLeft())
28
+
29
+ # Apply theme
30
+ self.apply_theme()
31
+
32
+ def apply_theme(self):
33
+ current_theme = self.settings.get_theme()
34
+ themes = {
35
+ "dark": {
36
+ "bg_color": "#2b2b2b",
37
+ "fg_color": "#ffffff",
38
+ "button_bg_color": "#3a3a3a",
39
+ "button_border_color": "#5a5a5a",
40
+ "button_hover_bg_color": "#4a4a4a",
41
+ "input_bg_color": "#3a3a3a",
42
+ "input_border_color": "#5a5a5a",
43
+ "menu_bg_color": "#2b2b2b",
44
+ "menu_item_hover_bg_color": "#3a3a3a",
45
+ "divider_color": "#444444"
46
+ },
47
+ "light": {
48
+ "bg_color": "#f0f0f0",
49
+ "fg_color": "#000000",
50
+ "button_bg_color": "#e0e0e0",
51
+ "button_border_color": "#c0c0c0",
52
+ "button_hover_bg_color": "#d0d0d0",
53
+ "input_bg_color": "#ffffff",
54
+ "input_border_color": "#c0c0c0",
55
+ "menu_bg_color": "#f0f0f0",
56
+ "menu_item_hover_bg_color": "#e0e0e0",
57
+ "divider_color": "#c0c0c0"
58
+ }
59
+ }
60
+
61
+ theme = themes["dark"] if current_theme == "dark" else themes["light"]
62
+ qdarktheme.setup_theme(current_theme)
63
+
64
+ self.setStyleSheet(f"""
65
+ QMainWindow {{
66
+ background-color: {theme['bg_color']};
67
+ color: {theme['fg_color']};
68
+ }}
69
+ QWidget {{
70
+ background-color: {theme['bg_color']};
71
+ color: {theme['fg_color']};
72
+ }}
73
+ QPushButton {{
74
+ background-color: {theme['button_bg_color']};
75
+ border: 1px solid {theme['button_border_color']};
76
+ padding: 5px;
77
+ min-width: 80px;
78
+ }}
79
+ QPushButton:hover {{
80
+ background-color: {theme['button_hover_bg_color']};
81
+ }}
82
+ QLineEdit {{
83
+ background-color: {theme['input_bg_color']};
84
+ border: 1px solid {theme['input_border_color']};
85
+ padding: 5px;
86
+ }}
87
+ QComboBox {{
88
+ background-color: {theme['input_bg_color']};
89
+ border: 1px solid {theme['input_border_color']};
90
+ padding: 5px;
91
+ }}
92
+ QComboBox:hover {{
93
+ background-color: {theme['button_hover_bg_color']};
94
+ }}
95
+ QComboBox::drop-down {{
96
+ border: none;
97
+ }}
98
+ QComboBox::down-arrow {{
99
+ image: none;
100
+ border: none;
101
+ }}
102
+ QGroupBox {{
103
+ border: 1px solid {theme['button_border_color']};
104
+ margin-top: 1em;
105
+ padding-top: 0.5em;
106
+ }}
107
+ QGroupBox::title {{
108
+ subcontrol-origin: margin;
109
+ left: 10px;
110
+ padding: 0 3px 0 3px;
111
+ }}
112
+ QRadioButton {{
113
+ color: {theme['fg_color']};
114
+ }}
115
+ QRadioButton::indicator {{
116
+ width: 13px;
117
+ height: 13px;
118
+ }}
119
+ QRadioButton::indicator:checked {{
120
+ background-color: {theme['button_hover_bg_color']};
121
+ border: 2px solid {theme['button_border_color']};
122
+ border-radius: 7px;
123
+ }}
124
+ QRadioButton::indicator:unchecked {{
125
+ background-color: {theme['bg_color']};
126
+ border: 2px solid {theme['button_border_color']};
127
+ border-radius: 7px;
128
+ }}
129
+ QLabel {{
130
+ color: {theme['fg_color']};
131
+ }}
132
+ """)
133
+
134
+ def showEvent(self, event):
135
+ """Override showEvent to apply theme when window is shown"""
136
+ super().showEvent(event)
137
+ self.apply_theme()
138
 
139
  def init_ui(self):
140
  try:
src/views/OffTargetView.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PyQt6 import QtWidgets, uic, QtCore
2
+ from utils.ui import show_error
3
+
4
+ class OffTargetView(QtWidgets.QMainWindow):
5
+ def __init__(self, global_settings):
6
+ try:
7
+ super().__init__()
8
+ self.global_settings = global_settings
9
+ self.logger = global_settings.get_logger()
10
+
11
+ uic.loadUi(global_settings.get_ui_dir_path() + '/off_target.ui', self)
12
+ self.setWindowTitle("Off-Target Analysis")
13
+
14
+ self._init_ui_components()
15
+
16
+ self.apply_theme()
17
+
18
+ except Exception as e:
19
+ show_error(self.global_settings, "Error initializing OffTargetView", str(e))
20
+
21
+ def _init_ui_components(self):
22
+ """Initialize UI components and set default values"""
23
+ try:
24
+ self._init_grpStep1()
25
+ self._init_grpStep2()
26
+ self._init_grpStep3()
27
+
28
+ self.push_button_cancel = self.findChild(QtWidgets.QPushButton, 'pbtnCancel')
29
+ self.push_button_submit = self.findChild(QtWidgets.QPushButton, 'pbtnSubmit')
30
+
31
+ self.setStyleSheet(self.global_settings.get_stylesheet())
32
+
33
+ except Exception as e:
34
+ show_error(self.global_settings, "Error initializing UI components", str(e))
35
+
36
+ def _init_grpStep1(self):
37
+ self.combo_box_organism = self.findChild(QtWidgets.QComboBox, 'cmbOrganism')
38
+ self.combo_box_endonuclease = self.findChild(QtWidgets.QComboBox, 'cmbEndonuclease')
39
+
40
+ def _init_grpStep2(self):
41
+ self.double_spin_box_tolerance = self.findChild(QtWidgets.QDoubleSpinBox, 'dspnTolerance')
42
+ self.combo_box_max_mismatches = self.findChild(QtWidgets.QComboBox, 'cmbMaxNoMismatches')
43
+
44
+ self.radio_button_average_output_yes = self.findChild(QtWidgets.QRadioButton, 'rbtnAverageOutputYes')
45
+ self.radio_button_average_output_no = self.findChild(QtWidgets.QRadioButton, 'rbtnAverageOutputNo')
46
+
47
+ self.radio_button_save_output_yes = self.findChild(QtWidgets.QRadioButton, 'rbtnSaveOutputFileYes')
48
+ self.radio_button_save_output_no = self.findChild(QtWidgets.QRadioButton, 'rbtnSaveOutputFileNo')
49
+
50
+ self.average_output_group = QtWidgets.QButtonGroup(self)
51
+ self.average_output_group.addButton(self.radio_button_average_output_yes)
52
+ self.average_output_group.addButton(self.radio_button_average_output_no)
53
+
54
+ self.save_output_group = QtWidgets.QButtonGroup(self)
55
+ self.save_output_group.addButton(self.radio_button_save_output_yes)
56
+ self.save_output_group.addButton(self.radio_button_save_output_no)
57
+
58
+ self.radio_button_average_output_no.setChecked(True)
59
+ self.radio_button_save_output_no.setChecked(True)
60
+
61
+ self.line_edit_output_file = self.findChild(QtWidgets.QLineEdit, 'ledSaveOutputFile')
62
+ self.radio_button_save_output_yes.toggled.connect(self._on_save_output_toggled)
63
+ self.line_edit_output_file.setEnabled(False) # Initially disabled
64
+
65
+ def _init_grpStep3(self):
66
+ """Initialize progress bar only"""
67
+ self.prog_bar = self.findChild(QtWidgets.QProgressBar, 'progBar')
68
+
69
+ self.prog_bar.setMinimum(0)
70
+ self.prog_bar.setMaximum(100)
71
+ self.prog_bar.setValue(0)
72
+
73
+ def _on_save_output_toggled(self, checked):
74
+ self.line_edit_output_file.setEnabled(checked)
75
+ if not checked:
76
+ self.line_edit_output_file.clear()
77
+
78
+ def apply_theme(self):
79
+ """Apply the current theme"""
80
+ if self.global_settings.get_theme() == "dark":
81
+ self.setStyleSheet(self.global_settings.get_dark_stylesheet())
82
+ else:
83
+ self.setStyleSheet(self.global_settings.get_light_stylesheet())
84
+
85
+ def get_analysis_parameters(self):
86
+ """Get all parameters needed for off-target analysis"""
87
+ output_filename = self.line_edit_output_file.text().strip()
88
+ save_output = self.radio_button_save_output_yes.isChecked()
89
+
90
+ return {
91
+ 'organism': self.combo_box_organism.currentText(),
92
+ 'endonuclease': self.combo_box_endonuclease.currentText(),
93
+ 'max_mismatches': int(self.combo_box_max_mismatches.currentText()),
94
+ 'tolerance': self.double_spin_box_tolerance.value(),
95
+ 'average_output': self.radio_button_average_output_yes.isChecked(),
96
+ 'save_output': save_output,
97
+ 'output_filename': output_filename if save_output else ''
98
+ }
99
+
100
+ def set_combo_box_organism(self, organisms):
101
+ self.combo_box_organism.clear()
102
+ self.combo_box_organism.addItems(organisms)
103
+
104
+ def set_combo_box_endonuclease(self, endonucleases):
105
+ self.combo_box_endonuclease.clear()
106
+ self.combo_box_endonuclease.addItems(endonucleases)
107
+
108
+ def set_combo_box_max_mismatches(self, max_mismatches=10):
109
+ self.combo_box_max_mismatches.clear()
110
+ self.combo_box_max_mismatches.addItems([str(mismatch) for mismatch in range(max_mismatches)])
111
+ self.combo_box_max_mismatches.setCurrentIndex(3)
112
+
113
+ def update_progress_bar(self, value, status=""):
114
+ self.prog_bar.setValue(value)
115
+ if status:
116
+ self.logger.debug(f"Progress: {status}")
117
+
118
+ def show_error(self, title, message):
119
+ QtWidgets.QMessageBox.critical(self, title, message)
120
+
121
+ def show_warning(self, title, message):
122
+ QtWidgets.QMessageBox.warning(self, title, message)
123
+
124
+ def closeEvent(self, event):
125
+ try:
126
+ self.push_button_cancel.clicked.emit()
127
+ event.accept()
128
+ except Exception as e:
129
+ self.logger.error(f"Error in closeEvent: {str(e)}")
130
+ event.accept()
131
+
132
+ def set_endonucleases(self, endonucleases):
133
+ """Set available endonucleases in combo box"""
134
+ try:
135
+ self.combo_box_endonuclease.clear()
136
+ self.combo_box_endonuclease.addItems(endonucleases)
137
+ except Exception as e:
138
+ self.logger.error(f"Error setting endonucleases: {str(e)}")
src/views/PopulationAnalysisWindowView.py CHANGED
@@ -2,16 +2,22 @@ from PyQt6 import QtWidgets, uic, QtGui, QtCore
2
  from PyQt6.QtWidgets import QHeaderView, QAbstractItemView
3
  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
4
  from matplotlib.figure import Figure
 
5
  import mplcursors
6
  import numpy as np
7
  import matplotlib.patches as patches
8
  from utils.ui import show_error
 
9
 
10
  class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
11
  def __init__(self, global_settings):
12
  super().__init__()
13
  self.settings = global_settings
14
  self.logger = self.settings.get_logger()
 
 
 
 
15
  self.init_ui()
16
 
17
  def init_ui(self):
@@ -24,26 +30,50 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
24
  def _init_ui_components(self):
25
  self._init_grpSelectOrganisms()
26
  self._init_grpSeedAnalysis()
27
- # self._init_colormap()
 
28
 
29
  def _init_grpSelectOrganisms(self):
30
- self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
31
- print(self.combo_box_endonuclease)
32
- self.table_organism = self._find_widget('tblOrganism', QtWidgets.QTableWidget)
33
- self.push_button_analyze_organism = self._find_widget('pbtnAnalyzeOrganism', QtWidgets.QPushButton)
34
-
35
- self.tab_widget_shared_seeds_heatmap = self._find_widget('tabsSharedSeedHeatmap', QtWidgets.QTabWidget)
36
- self.tab_shared_seed_heatmap = self._find_widget('tabSharedSeedHeatmap', QtWidgets.QWidget)
37
- self.heatmap_seed = self._find_widget('heatmapSeed', QtWidgets.QWidget)
38
-
39
- self.table_organism.setColumnCount(1)
40
- self.table_organism.setShowGrid(False)
41
- self.table_organism.setHorizontalHeaderLabels(["Organism"])
42
- self.table_organism.horizontalHeader().setSectionsClickable(True)
43
- self.table_organism.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
44
- self.table_organism.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
45
- self.table_organism.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
46
- self.table_organism.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  def _init_grpSeedAnalysis(self):
49
  self.line_edit_seed = self._find_widget('ledSeed', QtWidgets.QLineEdit)
@@ -76,15 +106,6 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
76
  self.table_locations.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
77
  self.table_locations.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
78
 
79
- # def _init_colormap(self):
80
- # self.colormap_figure = self._find_widget('wgtColormap', QtWidgets.QWidget)
81
- # if self.colormap_figure:
82
- # self.colormap_layout = QtWidgets.QVBoxLayout()
83
- # self.colormap_layout.setContentsMargins(0, 0, 0, 0)
84
- # self.colormap_canvas = MplCanvas(self)
85
- # self.colormap_layout.addWidget(self.colormap_canvas)
86
- # self.colormap_figure.setLayout(self.colormap_layout)
87
-
88
  def _find_widget(self, name: str, widget_type: type) -> QtWidgets.QWidget:
89
  widget = self.findChild(widget_type, name)
90
  if widget is None:
@@ -135,36 +156,98 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
135
  self.table_locations.resizeColumnsToContents()
136
 
137
  def plot_heatmap(self, data, labels):
138
- self.colormap_canvas.axes.clear()
139
- im = self.colormap_canvas.axes.imshow(data, cmap='summer')
140
- self.colormap_canvas.cbar = self.colormap_canvas.axes.figure.colorbar(im, ax=self.colormap_canvas.axes)
141
- self.colormap_canvas.cbar.ax.set_ylabel("", rotation=-90, va="bottom", fontsize=8)
142
-
143
- cursor = mplcursors.cursor(im, hover=True)
144
- @cursor.connect("add")
145
- def on_add(sel):
146
- sel.annotation.arrow_patch.set(arrowstyle="simple", fc="white", alpha=.5)
147
- sel.annotation.set_bbox(None)
148
- i, j = sel.target.index
149
- sel.annotation.set_text(labels[i][j])
150
-
151
- ax = self.colormap_canvas.axes
152
- ax.set_xticks(np.arange(len(data)))
153
- ax.set_yticks(np.arange(len(data)))
154
- ax.set_xticklabels(range(1, len(data) + 1))
155
- ax.set_yticklabels(range(1, len(data) + 1))
156
- ax.set_xlabel("Organism", fontsize=10)
157
- ax.set_ylabel("Organism", fontsize=10)
158
- ax.tick_params(axis='both', which='major', labelsize=8)
159
-
160
- def plot_cell_grid(data, ax=None, **kwargs):
161
- for x in range(data[0]):
162
- for y in range(data[1]):
163
- rect = patches.Rectangle((x - .5, y - .5), 1, 1, fill=False, **kwargs)
164
- ax.add_patch(rect)
165
-
166
- plot_cell_grid([len(data), len(data)], ax, color="black", linewidth=1)
167
- self.colormap_canvas.draw()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  def clear_shared_seeds_table(self):
170
  self.table_seed.setRowCount(0)
@@ -200,12 +283,47 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
200
  def sort_loc_finder_table(self, column):
201
  self.loc_finder_table.sortItems(column)
202
 
203
- class MplCanvas(FigureCanvasQTAgg):
204
- def __init__(self, parent=None, width=400, height=250, dpi=100):
205
  try:
206
- fig = Figure(dpi=dpi, tight_layout=True)
207
- self.axes = fig.add_subplot(111)
208
- self.axes.clear()
209
- super(MplCanvas, self).__init__(fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  except Exception as e:
211
- show_error("Error initializing MplCanvas class in population analysis.", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  from PyQt6.QtWidgets import QHeaderView, QAbstractItemView
3
  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
4
  from matplotlib.figure import Figure
5
+ import matplotlib.pyplot as plt
6
  import mplcursors
7
  import numpy as np
8
  import matplotlib.patches as patches
9
  from utils.ui import show_error
10
+ import copy
11
 
12
  class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
13
  def __init__(self, global_settings):
14
  super().__init__()
15
  self.settings = global_settings
16
  self.logger = self.settings.get_logger()
17
+
18
+ # Connect to theme change signal
19
+ self.settings.theme_changed.connect(self._on_theme_changed)
20
+
21
  self.init_ui()
22
 
23
  def init_ui(self):
 
30
  def _init_ui_components(self):
31
  self._init_grpSelectOrganisms()
32
  self._init_grpSeedAnalysis()
33
+
34
+ self.push_button_export_selected_gRNAs = self._find_widget('pbtnExportSelectedgRNAs', QtWidgets.QPushButton)
35
 
36
  def _init_grpSelectOrganisms(self):
37
+ try:
38
+ self.logger.debug("Starting _init_grpSelectOrganisms")
39
+
40
+ self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
41
+ self.table_organism = self._find_widget('tblOrganism', QtWidgets.QTableWidget)
42
+ self.push_button_analyze_organism = self._find_widget('pbtnAnalyzeOrganism', QtWidgets.QPushButton)
43
+
44
+ # Find the tab widget and heatmap widget
45
+ self.tab_widget_shared_seeds_heatmap = self._find_widget('tabsSharedSeedHeatmap', QtWidgets.QTabWidget)
46
+ self.tab_shared_seed_heatmap = self._find_widget('tabSharedSeedHeatmap', QtWidgets.QWidget)
47
+ self.heatmap_seed = self._find_widget('heatmapSeed', QtWidgets.QWidget)
48
+
49
+ self.logger.debug(f"Tab widget found: {self.tab_widget_shared_seeds_heatmap is not None}")
50
+ self.logger.debug(f"Heatmap widget found: {self.heatmap_seed is not None}")
51
+
52
+ # Create layout for heatmap
53
+ self.colormap_layout = QtWidgets.QVBoxLayout(self.heatmap_seed)
54
+ self.colormap_layout.setContentsMargins(0, 0, 0, 0)
55
+
56
+ # Create the matplotlib canvas
57
+ self.colormap_canvas = MplCanvas(self)
58
+ self.colormap_layout.addWidget(self.colormap_canvas)
59
+
60
+ # Set up the organism table
61
+ self.logger.debug("Setting up organism table")
62
+ self.table_organism.setColumnCount(1)
63
+ self.table_organism.setShowGrid(False)
64
+ self.table_organism.setHorizontalHeaderLabels(["Organism"])
65
+ self.table_organism.horizontalHeader().setSectionsClickable(True)
66
+ self.table_organism.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
67
+ self.table_organism.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
68
+ self.table_organism.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
69
+ self.table_organism.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
70
+
71
+ self.logger.debug("Completed _init_grpSelectOrganisms")
72
+
73
+ except Exception as e:
74
+ self.logger.error(f"Error in _init_grpSelectOrganisms: {str(e)}")
75
+ self.logger.exception("Full traceback:")
76
+ show_error(self.settings, "Error initializing select organisms group", str(e))
77
 
78
  def _init_grpSeedAnalysis(self):
79
  self.line_edit_seed = self._find_widget('ledSeed', QtWidgets.QLineEdit)
 
106
  self.table_locations.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
107
  self.table_locations.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
108
 
 
 
 
 
 
 
 
 
 
109
  def _find_widget(self, name: str, widget_type: type) -> QtWidgets.QWidget:
110
  widget = self.findChild(widget_type, name)
111
  if widget is None:
 
156
  self.table_locations.resizeColumnsToContents()
157
 
158
  def plot_heatmap(self, data, labels):
159
+ """Plot heatmap of shared seeds between organisms"""
160
+ try:
161
+ self.logger.debug("Starting plot_heatmap")
162
+ self.logger.debug(f"Data shape: {np.array(data).shape}")
163
+ self.logger.debug(f"Data: {data}")
164
+ self.logger.debug(f"Labels: {labels}")
165
+
166
+ # Clear the previous plot and colorbar safely
167
+ self.colormap_canvas.axes.clear()
168
+ if hasattr(self.colormap_canvas, 'cbar'):
169
+ try:
170
+ self.colormap_canvas.cbar.remove()
171
+ except:
172
+ self.logger.debug("Could not remove old colorbar, creating new figure")
173
+ # If colorbar removal fails, create new figure and canvas
174
+ self.colormap_canvas.fig.clear()
175
+ self.colormap_canvas.axes = self.colormap_canvas.fig.add_subplot(111)
176
+
177
+ # Create a copy of data for labels
178
+ labels_data = copy.deepcopy(data)
179
+
180
+ # Set diagonal elements to 0 for visualization
181
+ for i in range(len(data)):
182
+ data[i][i] = 0
183
+
184
+ # Create the heatmap
185
+ self.logger.debug("Creating heatmap")
186
+ im = self.colormap_canvas.axes.imshow(data, cmap='summer')
187
+
188
+ # Add colorbar
189
+ self.logger.debug("Adding colorbar")
190
+ self.colormap_canvas.cbar = self.colormap_canvas.fig.colorbar(im, ax=self.colormap_canvas.axes)
191
+
192
+ # Determine text color based on theme
193
+ text_color = 'white' if self.settings.get_theme() == 'dark' else 'black'
194
+
195
+ self.colormap_canvas.cbar.ax.set_ylabel("", rotation=-90, va="bottom", fontsize=8, color=text_color)
196
+ self.colormap_canvas.cbar.ax.tick_params(colors=text_color)
197
+
198
+ # Add hover annotations
199
+ self.logger.debug("Setting up hover annotations")
200
+ cursor = mplcursors.cursor(im, hover=True)
201
+ @cursor.connect("add")
202
+ def on_add(sel):
203
+ sel.annotation.arrow_patch.set(arrowstyle="simple", fc="white", alpha=.5)
204
+ sel.annotation.set_bbox(None)
205
+ i, j = sel.target.index
206
+ # Show the actual number of shared seeds
207
+ sel.annotation.set_text(str(labels_data[i][j]))
208
+
209
+ # Set up axes
210
+ self.logger.debug("Setting up axes")
211
+ ax = self.colormap_canvas.axes
212
+ ax.set_xticks(np.arange(len(data)))
213
+ ax.set_yticks(np.arange(len(data)))
214
+
215
+ # Use numbers for both x-axis and y-axis
216
+ x_labels = [str(i+1) for i in range(len(data))]
217
+ y_labels = [str(i+1) for i in range(len(data))] # Removed "Organism" prefix
218
+ ax.set_xticklabels(x_labels, color=text_color)
219
+ ax.set_yticklabels(y_labels, color=text_color)
220
+
221
+ # Rotate labels
222
+ self.logger.debug("Rotating labels")
223
+ plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
224
+
225
+ # Add grid
226
+ self.logger.debug("Adding grid")
227
+ for i in range(len(data)):
228
+ for j in range(len(data)):
229
+ ax.add_patch(patches.Rectangle(
230
+ (j - 0.5, i - 0.5), 1, 1,
231
+ fill=False, color="black", linewidth=1
232
+ ))
233
+
234
+ ax.set_xlabel("Organism", fontsize=10, color=text_color)
235
+ ax.set_ylabel("Organism", fontsize=10, color=text_color)
236
+ ax.tick_params(axis='both', which='major', labelsize=8, colors=text_color)
237
+
238
+ # Adjust layout and draw
239
+ self.logger.debug("Adjusting layout")
240
+ self.colormap_canvas.fig.tight_layout()
241
+
242
+ self.logger.debug("Drawing canvas")
243
+ self.colormap_canvas.draw()
244
+
245
+ self.logger.debug("Completed plot_heatmap")
246
+
247
+ except Exception as e:
248
+ self.logger.error(f"Error plotting heatmap: {str(e)}")
249
+ self.logger.exception("Full traceback:")
250
+ show_error(self.settings, "Error plotting heatmap", str(e))
251
 
252
  def clear_shared_seeds_table(self):
253
  self.table_seed.setRowCount(0)
 
283
  def sort_loc_finder_table(self, column):
284
  self.loc_finder_table.sortItems(column)
285
 
286
+ def _on_theme_changed(self, theme):
287
+ """Handle theme changes by updating the plot"""
288
  try:
289
+ if hasattr(self, 'colormap_canvas') and hasattr(self.colormap_canvas, 'axes'):
290
+ text_color = 'white' if theme == 'dark' else 'black'
291
+
292
+ # Update axis labels
293
+ self.colormap_canvas.axes.xaxis.label.set_color(text_color)
294
+ self.colormap_canvas.axes.yaxis.label.set_color(text_color)
295
+
296
+ # Update tick labels
297
+ self.colormap_canvas.axes.tick_params(colors=text_color)
298
+
299
+ # Update colorbar if it exists
300
+ if hasattr(self.colormap_canvas, 'cbar'):
301
+ self.colormap_canvas.cbar.ax.set_ylabel("", rotation=-90, va="bottom", fontsize=8, color=text_color)
302
+ self.colormap_canvas.cbar.ax.tick_params(colors=text_color)
303
+
304
+ # Redraw the canvas
305
+ self.colormap_canvas.draw()
306
+
307
  except Exception as e:
308
+ self.logger.error(f"Error updating plot theme: {str(e)}")
309
+
310
+ class MplCanvas(FigureCanvasQTAgg):
311
+ def __init__(self, parent=None, width=8, height=6, dpi=100):
312
+ self.fig = Figure(figsize=(width, height), dpi=dpi)
313
+ self.axes = self.fig.add_subplot(111)
314
+ super().__init__(self.fig)
315
+
316
+ # Set background colors based on current theme
317
+ self.update_colors(parent.settings.get_theme() if parent else 'light')
318
+
319
+ # Enable tight layout
320
+ self.fig.tight_layout()
321
+
322
+ def update_colors(self, theme):
323
+ """Update figure and axes colors based on theme"""
324
+ if theme == 'dark':
325
+ self.fig.patch.set_facecolor('none')
326
+ self.axes.set_facecolor('#2d2d2d') # Dark background for plot area
327
+ else:
328
+ self.fig.patch.set_facecolor('none')
329
+ self.axes.set_facecolor('white')
src/views/ViewTargetsView.py CHANGED
@@ -13,7 +13,7 @@ class ViewTargetsView(QtWidgets.QMainWindow):
13
 
14
  def __init__(self, global_settings):
15
  super().__init__()
16
- self.settings = global_settings
17
  self.logger = self.settings.get_logger()
18
 
19
  self.init_ui()
@@ -30,25 +30,42 @@ class ViewTargetsView(QtWidgets.QMainWindow):
30
  self._init_grpGuideAnalysis()
31
  self._init_grpGeneViewer()
32
 
33
- self.push_button_export_grna = self._find_widget('pbtnExportgRNA', QtWidgets.QPushButton)
34
-
35
- # Connect gene selection change with direct signal
36
- self.combo_box_gene.currentTextChanged.connect(self._on_gene_changed)
37
 
38
  def _init_grpGuideViewer(self):
39
  self.combo_box_gene = self._find_widget('cmbGene', QtWidgets.QComboBox)
40
  self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
 
 
41
  self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
42
- self.push_button_filter_options = self._find_widget('pbtnFilterOptions', QtWidgets.QPushButton)
43
  self.push_button_scoring_options = self._find_widget('pbtnScoringOptions', QtWidgets.QPushButton)
44
- self.table_targets = self._find_widget('tblTargets', QtWidgets.QTableWidget)
 
 
45
 
46
- self.table_targets.setColumnCount(8)
47
- self.table_targets.setHorizontalHeaderLabels(["Location", "Endonuclease", "Sequence", "Strand", "PAM", "Score", "Off-Target", "Details"])
48
- self.table_targets.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
49
- self.table_targets.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
50
- self.table_targets.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
51
- self.table_targets.horizontalHeader().setSectionResizeMode(7, QtWidgets.QHeaderView.ResizeMode.Stretch)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  def _init_grpGuideAnalysis(self):
54
  self.push_button_off_target = self._find_widget('pbtnOffTarget', QtWidgets.QPushButton)
@@ -63,52 +80,71 @@ class ViewTargetsView(QtWidgets.QMainWindow):
63
  self.text_edit_gene_viewer = self._find_widget('txtedGeneViewer', QtWidgets.QTextEdit)
64
  self.push_button_reset_location = self._find_widget('pbtnResetLocation', QtWidgets.QPushButton)
65
 
 
 
66
  def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
67
  widget = self.findChild(widget_type, name)
68
  if widget is None:
69
  self.logger.warning(f"Widget '{name}' not found in UI file.")
70
  return widget
71
 
72
- def display_targets_in_table(self, targets):
73
- """Ultra-fast target display using virtual table and minimal UI updates"""
74
  try:
75
- start_time = time.time()
 
76
 
77
- # Store complete set of targets if not already stored
78
- if not hasattr(self, '_complete_targets'):
79
- self._complete_targets = targets
80
-
81
- # Filter targets for currently selected gene
82
  selected_text = self.combo_box_gene.currentText()
83
- # Extract locus tag from "locus_tag: gene_name" format
84
- selected_locus = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
85
 
86
- if selected_locus:
87
- # Filter targets with more robust comparison
88
- filtered_targets = []
89
- for target in self._complete_targets:
90
- target_locus = str(target.get('feature_id', '')).strip()
91
- if target_locus.lower() == selected_locus.lower():
92
- filtered_targets.append(target)
93
-
94
- # Store filtered results
95
- self._all_results = filtered_targets
96
  else:
97
- filtered_targets = self._complete_targets
98
- self._all_results = filtered_targets
99
 
100
- total_rows = len(filtered_targets)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  # Completely freeze UI
103
  self.setUpdatesEnabled(False)
104
- self.table_targets.setUpdatesEnabled(False)
105
- self.table_targets.setSortingEnabled(False)
106
- self.table_targets.setVisible(False)
107
 
108
  try:
109
- # Pre-allocate table
110
- self.table_targets.clearContents()
111
- self.table_targets.setRowCount(total_rows)
112
 
113
  # Get current headers to check for Azimuth column
114
  headers = self.get_table_headers()
@@ -119,63 +155,80 @@ class ViewTargetsView(QtWidgets.QMainWindow):
119
 
120
  # Load ALL rows at once
121
  for row in range(total_rows):
122
- target = filtered_targets[row]
123
 
124
- # Create and set basic items
125
- for col, value in enumerate([
126
- target['location'], target['endonuclease'],
127
- target['sequence'], target['strand'], target['pam']
128
- ]):
129
- item = QTableWidgetItem(str(value))
130
- item.setFlags(flags)
131
- self.table_targets.setItem(row, col, item)
132
 
133
- # Handle score separately for numeric sorting
134
- score_item = QTableWidgetItem()
135
- score_item.setData(QtCore.Qt.ItemDataRole.EditRole, float(target['score']))
136
- self.table_targets.setItem(row, 5, score_item)
 
 
 
 
 
 
137
 
138
- # Add off-target placeholder
139
- ot_item = QTableWidgetItem("--.--")
140
- self.table_targets.setItem(row, 6, ot_item)
141
 
142
- # Create details button
143
- details_button = QtWidgets.QPushButton("Details")
144
- self.table_targets.setCellWidget(row, 7, details_button)
 
 
 
145
 
146
  # Add Azimuth score if column exists
147
- if azimuth_index is not None and 'azimuth_score' in target:
148
- azimuth_item = QTableWidgetItem()
149
- azimuth_item.setData(QtCore.Qt.ItemDataRole.EditRole, float(target['azimuth_score']))
150
- self.table_targets.setItem(row, azimuth_index, azimuth_item)
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- # Set column widths
153
- column_widths = [100, 100, 200, 80, 80, 80, 80, 100]
154
  for col, width in enumerate(column_widths):
155
- self.table_targets.setColumnWidth(col, width)
 
 
 
 
 
 
 
156
 
157
  finally:
158
  # Re-enable UI
159
- self.table_targets.setVisible(True)
160
- self.table_targets.setUpdatesEnabled(True)
161
  self.setUpdatesEnabled(True)
162
- self.table_targets.setSortingEnabled(True)
163
-
164
- total_time = time.time() - start_time
165
- self.logger.debug(f"Display time: {total_time:.2f} seconds for {total_rows} rows")
166
 
167
  except Exception as e:
168
- self.logger.error(f"Error in display_results: {str(e)}")
169
- show_error(self.settings, "Error displaying targets", str(e))
170
 
171
  def _handle_scroll_virtual(self, value, total_rows, row_height, buffer_rows):
172
- """Handle virtual scrolling with minimal updates"""
173
  try:
174
- if not hasattr(self, '_all_results') or not self._all_results:
175
  return
176
 
177
  # Calculate visible range with safety checks
178
- viewport_height = max(1, self.table_targets.viewport().height())
179
  row_height = max(1, row_height) # Ensure non-zero
180
  visible_rows = viewport_height // row_height
181
 
@@ -186,31 +239,31 @@ class ViewTargetsView(QtWidgets.QMainWindow):
186
 
187
  # Only update rows that aren't already loaded
188
  for row in range(start_row, end_row):
189
- if row < len(self._all_results) and not self.table_targets.item(row, 0):
190
- target = self._all_results[row]
191
 
192
  # Create and set items efficiently
193
  for col, value in enumerate([
194
- target['location'], target['endonuclease'],
195
- target['sequence'], target['strand'], target['pam'],
196
- target['score'], "--.--"
197
  ]):
198
  item = QTableWidgetItem(str(value))
199
  item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
200
- self.table_targets.setItem(row, col, item)
201
 
202
- if not self.table_targets.cellWidget(row, 7):
203
  details_button = QtWidgets.QPushButton("Details")
204
- self.table_targets.setCellWidget(row, 7, details_button)
205
 
206
  except Exception as e:
207
  self.logger.error(f"Error in _handle_scroll_virtual: {str(e)}")
208
 
209
- def get_selected_targets(self):
210
- """Get selected targets with all necessary data"""
211
  try:
212
- selected_rows = set(index.row() for index in self.table_targets.selectedIndexes())
213
- selected_targets = []
214
 
215
  # Get column indices once
216
  columns = {
@@ -219,54 +272,62 @@ class ViewTargetsView(QtWidgets.QMainWindow):
219
  'sequence': 2,
220
  'strand': 3,
221
  'pam': 4,
222
- 'score': 5
 
223
  }
224
 
225
- for row in sorted(selected_rows):
226
- # Verify all required cells have data
227
- if all(self.table_targets.item(row, col) is not None
228
- for col in columns.values()):
229
-
230
- target = {
231
- 'location': self.table_targets.item(row, columns['location']).text(),
232
- 'endonuclease': self.table_targets.item(row, columns['endonuclease']).text(),
233
- 'sequence': self.table_targets.item(row, columns['sequence']).text(),
234
- 'strand': self.table_targets.item(row, columns['strand']).text(),
235
- 'pam': self.table_targets.item(row, columns['pam']).text(),
236
- 'score': self.table_targets.item(row, columns['score']).text()
237
- }
238
- selected_targets.append(target)
239
- else:
240
- self.logger.warning(f"Skipping row {row} due to missing data")
 
 
 
 
 
 
 
 
 
 
241
 
242
- if not selected_targets:
243
- self.logger.warning("No valid targets selected")
244
 
245
- return selected_targets
246
 
247
  except Exception as e:
248
- self.logger.error(f"Error getting selected targets: {str(e)}")
249
- self.logger.error(f"Stack trace: {traceback.format_exc()}")
250
  return []
251
 
252
  def get_row_data(self, row):
253
  return {
254
- 'location': self.table_targets.item(row, 0).text(),
255
- 'endonuclease': self.table_targets.item(row, 1).text(),
256
- 'sequence': self.table_targets.item(row, 2).text(),
257
- 'strand': self.table_targets.item(row, 3).text(),
258
- 'pam': self.table_targets.item(row, 4).text(),
259
- 'score': self.table_targets.item(row, 5).text(),
260
- 'off_target': self.table_targets.item(row, 6).text()
261
  }
262
 
263
  def set_combo_box_endonuclease(self, endonucleases):
264
  self.combo_box_endonuclease.addItems(endonucleases)
265
 
266
  def set_combo_box_gene(self, genes):
267
- """Set genes in combo box with optimized performance"""
268
  try:
269
- start_time = time.time()
270
 
271
  # Disable UI updates
272
  self.combo_box_gene.blockSignals(True)
@@ -293,9 +354,6 @@ class ViewTargetsView(QtWidgets.QMainWindow):
293
  self.combo_box_gene.setUpdatesEnabled(True)
294
  self.combo_box_gene.blockSignals(False)
295
 
296
- total_time = time.time() - start_time
297
- self.logger.debug(f"Combo box update time: {total_time:.2f} seconds")
298
-
299
  except Exception as e:
300
  self.logger.error(f"Error setting genes in combo box: {str(e)}")
301
  self.logger.error(f"Stack trace: {traceback.format_exc()}")
@@ -322,33 +380,9 @@ class ViewTargetsView(QtWidgets.QMainWindow):
322
  doc.setHtml(sequence)
323
  self.text_edit_gene_viewer.setDocument(doc)
324
 
325
- def select_all_targets(self, select):
326
- for row in range(self.table_targets.rowCount()):
327
- self.table_targets.selectRow(row) if select else self.table_targets.clearSelection()
328
-
329
- def show_filter_options_dialog(self, options):
330
- # Implement this method to show filter options dialog
331
- pass
332
-
333
- def filter_options_accepted(self):
334
- # Implement this method to check if filter options were accepted
335
- return True
336
-
337
- def get_filter_options(self):
338
- # Implement this method to return new filter options
339
- return {}
340
-
341
- def show_scoring_options_dialog(self, options):
342
- # Implement this method to show scoring options dialog
343
- pass
344
-
345
- def scoring_options_accepted(self):
346
- # Implement this method to check if scoring options were accepted
347
- return True
348
-
349
- def get_scoring_options(self):
350
- # Implement this method to return new scoring options
351
- return {}
352
 
353
  def get_export_file_path(self):
354
  # Implement this method to get the export file path from the user
@@ -360,11 +394,11 @@ class ViewTargetsView(QtWidgets.QMainWindow):
360
  self.logger.debug(f"Gene selection changed to: {selected_text}")
361
 
362
  # Reset scroll position
363
- self.table_targets.verticalScrollBar().setValue(0)
364
 
365
  # Filter and display targets
366
  if hasattr(self, '_complete_targets'):
367
- self.display_targets_in_table(self._complete_targets)
368
 
369
  # Emit signal for controller to update gene sequence
370
  self.gene_selected.emit(selected_text)
@@ -376,30 +410,85 @@ class ViewTargetsView(QtWidgets.QMainWindow):
376
  def get_table_headers(self):
377
  """Get current table headers"""
378
  headers = []
379
- for i in range(self.table_targets.columnCount()):
380
- headers.append(self.table_targets.horizontalHeaderItem(i).text())
381
  return headers
382
 
383
  def add_scoring_column(self, algorithm_name, position=None):
384
  """Add a new column for alternative scoring method at specified position"""
385
  if position is None:
386
  # Add to end if no position specified
387
- position = self.table_targets.columnCount()
388
 
389
- self.table_targets.insertColumn(position)
390
- self.table_targets.setHorizontalHeaderItem(
391
  position,
392
  QtWidgets.QTableWidgetItem(algorithm_name)
393
  )
394
 
395
  # Shift any existing columns after the insertion point
396
- for i in range(self.table_targets.columnCount() - 1, position, -1):
397
- for row in range(self.table_targets.rowCount()):
398
- self.table_targets.setItem(row, i, self.table_targets.takeItem(row, i-1))
399
 
400
  # Move column header
401
- header_item = self.table_targets.takeHorizontalHeaderItem(i-1)
402
  if header_item:
403
- self.table_targets.setHorizontalHeaderItem(i, header_item)
404
 
405
  return position
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  def __init__(self, global_settings):
15
  super().__init__()
16
+ self.settings = global_settings
17
  self.logger = self.settings.get_logger()
18
 
19
  self.init_ui()
 
30
  self._init_grpGuideAnalysis()
31
  self._init_grpGeneViewer()
32
 
33
+ self.push_button_export_selected_grnas = self._find_widget('pbtnExportSelectedgRNAs', QtWidgets.QPushButton)
 
 
 
34
 
35
  def _init_grpGuideViewer(self):
36
  self.combo_box_gene = self._find_widget('cmbGene', QtWidgets.QComboBox)
37
  self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
38
+ self.check_box_filter_5_prime_g_sequences = self._find_widget('chkFilter5PrimeG', QtWidgets.QCheckBox)
39
+ self.spin_box_minimum_on_target_score = self._find_widget('spnMinOTScore', QtWidgets.QSpinBox)
40
  self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
 
41
  self.push_button_scoring_options = self._find_widget('pbtnScoringOptions', QtWidgets.QPushButton)
42
+ self.table_guides = self._find_widget('tblGuides', QtWidgets.QTableWidget)
43
+
44
+ self.combo_box_gene.currentTextChanged.connect(self._on_gene_changed)
45
 
46
+ self.table_guides.setColumnCount(8)
47
+ self.table_guides.setHorizontalHeaderLabels(["Location", "Endonuclease", "Sequence", "Strand", "PAM", "Score", "Off-Target", "Details"])
48
+ self.table_guides.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
49
+ self.table_guides.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
50
+ self.table_guides.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
51
+
52
+ # Enable horizontal scrolling
53
+ self.table_guides.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
54
+ self.table_guides.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
55
+
56
+ # Set size policy to allow table to shrink and expand
57
+ self.table_guides.setSizePolicy(
58
+ QtWidgets.QSizePolicy.Policy.Expanding,
59
+ QtWidgets.QSizePolicy.Policy.Expanding
60
+ )
61
+
62
+ # Set resize mode for header
63
+ header = self.table_guides.horizontalHeader()
64
+ header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Interactive)
65
+ header.setStretchLastSection(False) # Don't stretch the last section
66
+
67
+ # Set minimum section size to prevent columns from becoming too narrow
68
+ header.setMinimumSectionSize(80)
69
 
70
  def _init_grpGuideAnalysis(self):
71
  self.push_button_off_target = self._find_widget('pbtnOffTarget', QtWidgets.QPushButton)
 
80
  self.text_edit_gene_viewer = self._find_widget('txtedGeneViewer', QtWidgets.QTextEdit)
81
  self.push_button_reset_location = self._find_widget('pbtnResetLocation', QtWidgets.QPushButton)
82
 
83
+ self.text_edit_gene_viewer.setReadOnly(True)
84
+
85
  def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
86
  widget = self.findChild(widget_type, name)
87
  if widget is None:
88
  self.logger.warning(f"Widget '{name}' not found in UI file.")
89
  return widget
90
 
91
+ def display_guides_in_table(self, guides):
92
+ """Ultra-fast guide display with virtual table and minimal UI updates"""
93
  try:
94
+ # Store complete set of guides
95
+ self._all_guides = guides
96
 
 
 
 
 
 
97
  selected_text = self.combo_box_gene.currentText()
 
 
98
 
99
+ # First filter by position/feature
100
+ if selected_text and "chrom" in selected_text and "start:" in selected_text:
101
+ filtered_guides = []
102
+ for guide in self._all_guides:
103
+ if guide.get('feature_id') == selected_text:
104
+ filtered_guides.append(guide)
105
+ self.logger.debug(f"Filtered to {len(filtered_guides)} guides for position {selected_text}")
 
 
 
106
  else:
107
+ selected_locus = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
 
108
 
109
+ if selected_locus:
110
+ filtered_guides = []
111
+ for guide in self._all_guides:
112
+ guide_locus = str(guide.get('feature_id', '')).strip()
113
+ if guide_locus.lower() == selected_locus.lower():
114
+ filtered_guides.append(guide)
115
+ else:
116
+ filtered_guides = self._all_guides
117
+
118
+ # Apply additional filters
119
+ final_guides = []
120
+ for guide in filtered_guides:
121
+ # Filter by minimum score
122
+ min_score = self.spin_box_minimum_on_target_score.value()
123
+ if float(guide.get('score', 0)) < min_score:
124
+ continue
125
+
126
+ # Filter by 5' G sequences
127
+ if self.check_box_filter_5_prime_g_sequences.isChecked():
128
+ sequence = guide.get('sequence', '')
129
+ if not sequence or not sequence.startswith('G'):
130
+ continue
131
+
132
+ final_guides.append(guide)
133
+
134
+ # Update table with new guides
135
+ total_rows = len(final_guides)
136
+ self.logger.debug(f"Processing {total_rows} rows for display after filtering")
137
 
138
  # Completely freeze UI
139
  self.setUpdatesEnabled(False)
140
+ self.table_guides.setUpdatesEnabled(False)
141
+ self.table_guides.setSortingEnabled(False)
142
+ self.table_guides.setVisible(False)
143
 
144
  try:
145
+ # Clear and resize table
146
+ self.table_guides.clearContents()
147
+ self.table_guides.setRowCount(total_rows)
148
 
149
  # Get current headers to check for Azimuth column
150
  headers = self.get_table_headers()
 
155
 
156
  # Load ALL rows at once
157
  for row in range(total_rows):
158
+ guide = final_guides[row]
159
 
160
+ # Extract start position from location (format: "start-end")
161
+ location = guide['location']
162
+ start_pos = location.split('-')[0] if '-' in location else location
 
 
 
 
 
163
 
164
+ # Create and set basic items
165
+ items = [
166
+ (0, QTableWidgetItem(start_pos)), # Only show start position
167
+ (1, QTableWidgetItem(guide['endonuclease'])),
168
+ (2, QTableWidgetItem(guide['sequence'])),
169
+ (3, QTableWidgetItem(guide['strand'])),
170
+ (4, QTableWidgetItem(guide['pam'])),
171
+ (5, QTableWidgetItem(str(guide['score']))),
172
+ (6, QTableWidgetItem("--.--")) # Off-target placeholder
173
+ ]
174
 
175
+ for col, item in items:
176
+ item.setFlags(flags)
177
+ self.table_guides.setItem(row, col, item)
178
 
179
+ # Only add details button if sequence has off-target details
180
+ sequence = guide['sequence']
181
+ if hasattr(self, '_off_target_details') and sequence in self._off_target_details:
182
+ details_button = QtWidgets.QPushButton("Details")
183
+ details_button.clicked.connect(self._show_details)
184
+ self.table_guides.setCellWidget(row, 7, details_button)
185
 
186
  # Add Azimuth score if column exists
187
+ if azimuth_index is not None and 'azimuth_score' in guide:
188
+ azimuth_item = QTableWidgetItem(str(guide.get('azimuth_score', 0)))
189
+ self.table_guides.setItem(row, azimuth_index, azimuth_item)
190
+
191
+ # Updated column widths
192
+ column_widths = [
193
+ 80, # Location
194
+ 100, # Endonuclease
195
+ 200, # Sequence
196
+ 10, # Strand
197
+ 80, # PAM
198
+ 10, # Score
199
+ 30, # Off-Target
200
+ 80 # Details
201
+ ]
202
 
203
+ # Set the column widths
 
204
  for col, width in enumerate(column_widths):
205
+ self.table_guides.setColumnWidth(col, width)
206
+
207
+ essential_columns_width = sum(column_widths[:8]) # First 6 columns
208
+ self.table_guides.setMinimumWidth(essential_columns_width)
209
+
210
+ # Update the group box to properly handle scrolling
211
+ guide_viewer_group = self.findChild(QtWidgets.QGroupBox, 'grpGuideViewer')
212
+ guide_viewer_group.setMinimumWidth(essential_columns_width + 50) # Add some padding for scrollbar
213
 
214
  finally:
215
  # Re-enable UI
216
+ self.table_guides.setVisible(True)
217
+ self.table_guides.setUpdatesEnabled(True)
218
  self.setUpdatesEnabled(True)
219
+ self.table_guides.setSortingEnabled(True)
 
 
 
220
 
221
  except Exception as e:
222
+ self.logger.error(f"Error in display_guides: {str(e)}")
223
+ show_error(self.settings, "Error displaying guides", str(e))
224
 
225
  def _handle_scroll_virtual(self, value, total_rows, row_height, buffer_rows):
 
226
  try:
227
+ if not hasattr(self, '_all_guides') or not self._all_guides:
228
  return
229
 
230
  # Calculate visible range with safety checks
231
+ viewport_height = max(1, self.table_guides.viewport().height())
232
  row_height = max(1, row_height) # Ensure non-zero
233
  visible_rows = viewport_height // row_height
234
 
 
239
 
240
  # Only update rows that aren't already loaded
241
  for row in range(start_row, end_row):
242
+ if row < len(self._all_guides) and not self.table_guides.item(row, 0):
243
+ guide = self._all_guides[row]
244
 
245
  # Create and set items efficiently
246
  for col, value in enumerate([
247
+ guide['location'], guide['endonuclease'],
248
+ guide['sequence'], guide['strand'], guide['pam'],
249
+ guide['score'], "--.--"
250
  ]):
251
  item = QTableWidgetItem(str(value))
252
  item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
253
+ self.table_guides.setItem(row, col, item)
254
 
255
+ if not self.table_guides.cellWidget(row, 7):
256
  details_button = QtWidgets.QPushButton("Details")
257
+ self.table_guides.setCellWidget(row, 7, details_button)
258
 
259
  except Exception as e:
260
  self.logger.error(f"Error in _handle_scroll_virtual: {str(e)}")
261
 
262
+ def get_selected_guides(self):
263
+ """Get selected guides with all necessary data"""
264
  try:
265
+ selected_rows = sorted(set(item.row() for item in self.table_guides.selectedItems()))
266
+ selected_guides = []
267
 
268
  # Get column indices once
269
  columns = {
 
272
  'sequence': 2,
273
  'strand': 3,
274
  'pam': 4,
275
+ 'score': 5,
276
+ 'off_target': 6
277
  }
278
 
279
+ # Get current gene information from combo box
280
+ current_gene = self.combo_box_gene.currentText()
281
+ if ': ' in current_gene: # Format is "locus_tag: gene_name"
282
+ locus_tag, gene_name = current_gene.split(': ', 1)
283
+ else:
284
+ locus_tag = current_gene
285
+ gene_name = current_gene
286
+
287
+ for row in selected_rows:
288
+ # Create guide dictionary directly from table items
289
+ guide = {}
290
+ valid_row = True
291
+
292
+ for col_name, col_index in columns.items():
293
+ item = self.table_guides.item(row, col_index)
294
+ if item is None:
295
+ valid_row = False
296
+ self.logger.warning(f"Missing data in row {row}, column {col_name}")
297
+ break
298
+ guide[col_name] = item.text()
299
+
300
+ if valid_row:
301
+ # Add gene information
302
+ guide['locus_tag'] = locus_tag.strip()
303
+ guide['gene_name'] = gene_name.strip()
304
+ selected_guides.append(guide)
305
 
306
+ if not selected_guides:
307
+ self.logger.warning("No valid guides selected")
308
 
309
+ return selected_guides
310
 
311
  except Exception as e:
312
+ self.logger.error(f"Error getting selected guides: {str(e)}")
 
313
  return []
314
 
315
  def get_row_data(self, row):
316
  return {
317
+ 'location': self.table_guides.item(row, 0).text(),
318
+ 'endonuclease': self.table_guides.item(row, 1).text(),
319
+ 'sequence': self.table_guides.item(row, 2).text(),
320
+ 'strand': self.table_guides.item(row, 3).text(),
321
+ 'pam': self.table_guides.item(row, 4).text(),
322
+ 'score': self.table_guides.item(row, 5).text(),
323
+ 'off_target': self.table_guides.item(row, 6).text()
324
  }
325
 
326
  def set_combo_box_endonuclease(self, endonucleases):
327
  self.combo_box_endonuclease.addItems(endonucleases)
328
 
329
  def set_combo_box_gene(self, genes):
 
330
  try:
 
331
 
332
  # Disable UI updates
333
  self.combo_box_gene.blockSignals(True)
 
354
  self.combo_box_gene.setUpdatesEnabled(True)
355
  self.combo_box_gene.blockSignals(False)
356
 
 
 
 
357
  except Exception as e:
358
  self.logger.error(f"Error setting genes in combo box: {str(e)}")
359
  self.logger.error(f"Stack trace: {traceback.format_exc()}")
 
380
  doc.setHtml(sequence)
381
  self.text_edit_gene_viewer.setDocument(doc)
382
 
383
+ def select_all_guides(self, select):
384
+ for row in range(self.table_guides.rowCount()):
385
+ self.table_guides.selectRow(row) if select else self.table_guides.clearSelection()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
  def get_export_file_path(self):
388
  # Implement this method to get the export file path from the user
 
394
  self.logger.debug(f"Gene selection changed to: {selected_text}")
395
 
396
  # Reset scroll position
397
+ self.table_guides.verticalScrollBar().setValue(0)
398
 
399
  # Filter and display targets
400
  if hasattr(self, '_complete_targets'):
401
+ self.display_guides_in_table(self._complete_targets)
402
 
403
  # Emit signal for controller to update gene sequence
404
  self.gene_selected.emit(selected_text)
 
410
  def get_table_headers(self):
411
  """Get current table headers"""
412
  headers = []
413
+ for i in range(self.table_guides.columnCount()):
414
+ headers.append(self.table_guides.horizontalHeaderItem(i).text())
415
  return headers
416
 
417
  def add_scoring_column(self, algorithm_name, position=None):
418
  """Add a new column for alternative scoring method at specified position"""
419
  if position is None:
420
  # Add to end if no position specified
421
+ position = self.table_guides.columnCount()
422
 
423
+ self.table_guides.insertColumn(position)
424
+ self.table_guides.setHorizontalHeaderItem(
425
  position,
426
  QtWidgets.QTableWidgetItem(algorithm_name)
427
  )
428
 
429
  # Shift any existing columns after the insertion point
430
+ for i in range(self.table_guides.columnCount() - 1, position, -1):
431
+ for row in range(self.table_guides.rowCount()):
432
+ self.table_guides.setItem(row, i, self.table_guides.takeItem(row, i-1))
433
 
434
  # Move column header
435
+ header_item = self.table_guides.takeHorizontalHeaderItem(i-1)
436
  if header_item:
437
+ self.table_guides.setHorizontalHeaderItem(i, header_item)
438
 
439
  return position
440
+
441
+ def update_off_target_details(self, off_target_results, detailed_results=None):
442
+ """Update off-target scores and details"""
443
+ try:
444
+ # Store detailed results if provided
445
+ if detailed_results:
446
+ self._off_target_details = detailed_results
447
+
448
+ # Update off-target scores in table
449
+ for row in range(self.table_guides.rowCount()):
450
+ sequence = self.table_guides.item(row, 2).text()
451
+ if sequence in off_target_results:
452
+ score = off_target_results[sequence]
453
+ score_item = QTableWidgetItem(str(score))
454
+ self.table_guides.setItem(row, 6, score_item)
455
+
456
+ # Add details button if detailed results exist
457
+ if detailed_results and sequence in detailed_results:
458
+ details_button = QtWidgets.QPushButton("Details")
459
+ details_button.clicked.connect(self._show_details)
460
+ self.table_guides.setCellWidget(row, 7, details_button)
461
+
462
+ self.table_guides.resizeColumnsToContents()
463
+
464
+ except Exception as e:
465
+ self.logger.error(f"Error updating off-target details: {str(e)}")
466
+ show_error(self.settings, "Error updating off-target details", str(e))
467
+
468
+ def _show_details(self):
469
+ """Show off-target details dialog"""
470
+ try:
471
+ button = self.sender()
472
+ index = self.table_guides.indexAt(button.pos())
473
+ sequence = self.table_guides.item(index.row(), 2).text()
474
+
475
+ if sequence in self._off_target_details:
476
+ details = self._off_target_details[sequence]
477
+
478
+ msg = QtWidgets.QMessageBox()
479
+ msg.setWindowTitle("Details")
480
+
481
+ # Format details message
482
+ chromo_str = "<html><b>Reference gRNA:</b><br>Location, Sequence, Strand, PAM, On Score<br></html>"
483
+ input_str = (f"{self.table_guides.item(index.row(),0).text()}, {sequence}, "
484
+ f"{self.table_guides.item(index.row(),3).text()}, "
485
+ f"{self.table_guides.item(index.row(),4).text()}, "
486
+ f"{self.table_guides.item(index.row(),5).text()}<br><br>")
487
+ detail_str = "<html><b>Off-Target Hits:</b><br>Off Score, Chromosome, Location, Sequence<br></html>"
488
+
489
+ msg.setText(chromo_str + input_str + detail_str + "<br>".join(details))
490
+ msg.exec()
491
+
492
+ except Exception as e:
493
+ self.logger.error(f"Error showing details: {str(e)}")
494
+ show_error(self.settings, "Error showing details", str(e))